The changes to the NewsfeedContainer include adding the final pieces to post a media object and the ability to view a media post. A new method is created to render media components for images and videos. When a media file is selected from the gallery, the appropriate post type is created. Styled text posts can now be rendered with the background style applied.
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Creating a Facebook Clone - Part XLII - Transcript.pdf
1. Creating a Facebook Clone - Part XLII
The changes to NewsfeedContainer include the final pieces to post a media object & the ability to view a media post. While I was here I also fixed the code for displaying
a styled post which is something I never implemented in the code itself...
2. }
return stats;
}
private static Container createPostBar() {
Button avatar = new Button(ServerAPI.me().getAvatar(6.5f),"Label");
Button writePost = new Button("What's on your mind?",
"NewPostButton");
Button gallery = new Button("Photo", "GalleryButton");
FontImage.setMaterialIcon(gallery,
FontImage.MATERIAL_PHOTO_LIBRARY, 2.9f);
gallery.setTextPosition(BOTTOM);
Container c = BorderLayout.centerEastWest(writePost,gallery,avatar);
c.setUIID("HalfPaddedContainer");
writePost.addActionListener(e -> NewPostForm.createPost().show());
gallery.addActionListener(e -> {
ImagePicker p = new ImagePicker(GALLERY_ALL);
p.pick(
image -> {
NewPostForm.createImagePost(image, p).show();
},
video -> {
NewPostForm.createVideoPost(video, p).show();
});
});
NewsfeedContainer
We'll start at the bottom with createPostBar which is where we reference the new API's to post an image/video file.
This was changed from using the constructor to the new factory method
3. }
return stats;
}
private static Container createPostBar() {
Button avatar = new Button(ServerAPI.me().getAvatar(6.5f),"Label");
Button writePost = new Button("What's on your mind?",
"NewPostButton");
Button gallery = new Button("Photo", "GalleryButton");
FontImage.setMaterialIcon(gallery,
FontImage.MATERIAL_PHOTO_LIBRARY, 2.9f);
gallery.setTextPosition(BOTTOM);
Container c = BorderLayout.centerEastWest(writePost,gallery,avatar);
c.setUIID("HalfPaddedContainer");
writePost.addActionListener(e -> NewPostForm.createPost().show());
gallery.addActionListener(e -> {
ImagePicker p = new ImagePicker(GALLERY_ALL);
p.pick(
image -> {
NewPostForm.createImagePost(image, p).show();
},
video -> {
NewPostForm.createVideoPost(video, p).show();
});
});
NewsfeedContainer
The gallery picker launches in an "all" mode which will show both images & videos
4. private static Container createPostBar() {
Button avatar = new Button(ServerAPI.me().getAvatar(6.5f),"Label");
Button writePost = new Button("What's on your mind?",
"NewPostButton");
Button gallery = new Button("Photo", "GalleryButton");
FontImage.setMaterialIcon(gallery,
FontImage.MATERIAL_PHOTO_LIBRARY, 2.9f);
gallery.setTextPosition(BOTTOM);
Container c = BorderLayout.centerEastWest(writePost,gallery,avatar);
c.setUIID("HalfPaddedContainer");
writePost.addActionListener(e -> NewPostForm.createPost().show());
gallery.addActionListener(e -> {
ImagePicker p = new ImagePicker(GALLERY_ALL);
p.pick(
image -> {
NewPostForm.createImagePost(image, p).show();
},
video -> {
NewPostForm.createVideoPost(video, p).show();
});
});
return c;
}
}
NewsfeedContainer
We invoke the right type of post based on the user selection. That's a simple change and now we can actually do a media post!
It would actually work but won't show the image in the UI as the code within this class still can't render that. This is simple to fix.
5. return BoxLayout.encloseY(
titleArea, body, createPostStats(p), buttonBar);
}
private static Image placeholder;
private static Component createMediaComponent(String mime, String id) {
if(mime.startsWith("image")) {
if(placeholder == null) {
placeholder = Image.createImage(getDisplayWidth(),
getDisplayHeight() / 2, 0);
}
ScaleImageButton sb = new ScaleImageButton(
URLImage.createCachedImage(id, ServerAPI.mediaUrl(id),
placeholder, URLImage.FLAG_RESIZE_SCALE_TO_FILL));
return sb;
} else {
try {
Media media=MediaManager.createMedia(ServerAPI.mediaUrl(id),
true);
MediaPlayer mp = new MediaPlayer(media) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
NewsfeedContainer
First we need a new method that will create a Component to match the given media ID.
6. return BoxLayout.encloseY(
titleArea, body, createPostStats(p), buttonBar);
}
private static Image placeholder;
private static Component createMediaComponent(String mime, String id) {
if(mime.startsWith("image")) {
if(placeholder == null) {
placeholder = Image.createImage(getDisplayWidth(),
getDisplayHeight() / 2, 0);
}
ScaleImageButton sb = new ScaleImageButton(
URLImage.createCachedImage(id, ServerAPI.mediaUrl(id),
placeholder, URLImage.FLAG_RESIZE_SCALE_TO_FILL));
return sb;
} else {
try {
Media media=MediaManager.createMedia(ServerAPI.mediaUrl(id),
true);
MediaPlayer mp = new MediaPlayer(media) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
NewsfeedContainer
We'll use this image as a placeholder image for URLImage
7. return BoxLayout.encloseY(
titleArea, body, createPostStats(p), buttonBar);
}
private static Image placeholder;
private static Component createMediaComponent(String mime, String id) {
if(mime.startsWith("image")) {
if(placeholder == null) {
placeholder = Image.createImage(getDisplayWidth(),
getDisplayHeight() / 2, 0);
}
ScaleImageButton sb = new ScaleImageButton(
URLImage.createCachedImage(id, ServerAPI.mediaUrl(id),
placeholder, URLImage.FLAG_RESIZE_SCALE_TO_FILL));
return sb;
} else {
try {
Media media=MediaManager.createMedia(ServerAPI.mediaUrl(id),
true);
MediaPlayer mp = new MediaPlayer(media) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
NewsfeedContainer
We initialize it lazily to a size that makes sense for the device
8. return BoxLayout.encloseY(
titleArea, body, createPostStats(p), buttonBar);
}
private static Image placeholder;
private static Component createMediaComponent(String mime, String id) {
if(mime.startsWith("image")) {
if(placeholder == null) {
placeholder = Image.createImage(getDisplayWidth(),
getDisplayHeight() / 2, 0);
}
ScaleImageButton sb = new ScaleImageButton(
URLImage.createCachedImage(id, ServerAPI.mediaUrl(id),
placeholder, URLImage.FLAG_RESIZE_SCALE_TO_FILL));
return sb;
} else {
try {
Media media=MediaManager.createMedia(ServerAPI.mediaUrl(id),
true);
MediaPlayer mp = new MediaPlayer(media) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
NewsfeedContainer
Since the image is scaled in the download process there is no need for the calcPreferredSize trick here, we can just use a regular button or label
9. }
ScaleImageButton sb = new ScaleImageButton(
URLImage.createCachedImage(id, ServerAPI.mediaUrl(id),
placeholder, URLImage.FLAG_RESIZE_SCALE_TO_FILL));
return sb;
} else {
try {
Media media=MediaManager.createMedia(ServerAPI.mediaUrl(id),
true);
MediaPlayer mp = new MediaPlayer(media) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
mp.setLoop(true);
return mp;
} catch(IOException err) {
Log.e(err);
return new Label("Error loading media");
}
}
}
NewsfeedContainer
A media stream can be played directly from our server URL which is pretty darn cool!
10. }
ScaleImageButton sb = new ScaleImageButton(
URLImage.createCachedImage(id, ServerAPI.mediaUrl(id),
placeholder, URLImage.FLAG_RESIZE_SCALE_TO_FILL));
return sb;
} else {
try {
Media media=MediaManager.createMedia(ServerAPI.mediaUrl(id),
true);
MediaPlayer mp = new MediaPlayer(media) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
mp.setLoop(true);
return mp;
} catch(IOException err) {
Log.e(err);
return new Label("Error loading media");
}
}
}
NewsfeedContainer
We need to override preferred size here because the media can be any size. This is a bit over simplified. Proper server media handling will transcode the video to multiple
form factors and deliver the right video type for every device. This saves bandwidth but also makes sure the media is playable avoiding multiple device related issues.
11. FlowLayout.encloseIn(menu), avatar);
titleArea.setUIID("HalfPaddedContainer");
return titleArea;
}
public static Container createNewsItem(User u, Post p) {
Container titleArea = createNewsTitle(u, p);
Component body;
String style = null;
if(p.styling.get() != null && !p.styling.get().equals("Label")) {
style = p.styling.get();
}
if(p.content.get().indexOf('<') > -1) {
if(style != null) {
body = new RichTextView(p.content.get(), "PostStyleText");
((RichTextView)body).setAlignment(CENTER);
body.setUIID(style);
} else {
body = new RichTextView(p.content.get());
body.setUIID("HalfPaddedContainer");
}
} else {
body = new SpanLabel(p.content.get());
if(style != null) {
((SpanLabel)body).setTextUIID("PostStyleText");
NewsfeedContainer
Now that this is in place we can tie it in to the post rendering logic in createNewsItem
12. like.setUIID("CleanButton");
Button comment = new Button("Comment", "CleanButton");
Button share = new Button("Share", "CleanButton");
FontImage.setMaterialIcon(like, FontImage.MATERIAL_THUMB_UP);
FontImage.setMaterialIcon(comment,
FontImage.MATERIAL_COMMENT);
FontImage.setMaterialIcon(share, FontImage.MATERIAL_SHARE);
Container buttonBar = GridLayout.encloseIn(3, like, comment, share);
buttonBar.setUIID("HalfPaddedContainer");
like.setSelected(p.likes.contains(u));
like.addActionListener(e -> ServerAPI.like(p));
comment.addActionListener(e -> new CommentsForm(p, null).show());
if(p.attachments.size() > 0) {
String key = p.attachments.keySet().iterator().next();
return BoxLayout.encloseY(titleArea, body,
createMediaComponent(p.attachments.get(key), key),
createPostStats(p), buttonBar);
}
return BoxLayout.encloseY(
titleArea, body, createPostStats(p), buttonBar);
}
NewsfeedContainer
If we have an attachment we add the media component to the bottom of the post.
13. like.setUIID("CleanButton");
Button comment = new Button("Comment", "CleanButton");
Button share = new Button("Share", "CleanButton");
FontImage.setMaterialIcon(like, FontImage.MATERIAL_THUMB_UP);
FontImage.setMaterialIcon(comment,
FontImage.MATERIAL_COMMENT);
FontImage.setMaterialIcon(share, FontImage.MATERIAL_SHARE);
Container buttonBar = GridLayout.encloseIn(3, like, comment, share);
buttonBar.setUIID("HalfPaddedContainer");
like.setSelected(p.likes.contains(u));
like.addActionListener(e -> ServerAPI.like(p));
comment.addActionListener(e -> new CommentsForm(p, null).show());
if(p.attachments.size() > 0) {
String key = p.attachments.keySet().iterator().next();
return BoxLayout.encloseY(titleArea, body,
createMediaComponent(p.attachments.get(key), key),
createPostStats(p), buttonBar);
}
return BoxLayout.encloseY(
titleArea, body, createPostStats(p), buttonBar);
}
NewsfeedContainer
Otherwise we use the same post code as before. With that media posts will work and show up in your feed!
14. titleArea.setUIID("HalfPaddedContainer");
return titleArea;
}
public static Container createNewsItem(User u, Post p) {
Container titleArea = createNewsTitle(u, p);
Component body;
String style = null;
if(p.styling.get() != null && !p.styling.get().equals("Label")) {
style = p.styling.get();
}
if(p.content.get().indexOf('<') > -1) {
if(style != null) {
body = new RichTextView(p.content.get(), "PostStyleText");
((RichTextView)body).setAlignment(CENTER);
body.setUIID(style);
} else {
body = new RichTextView(p.content.get());
body.setUIID("HalfPaddedContainer");
}
} else {
body = new SpanLabel(p.content.get());
if(style != null) {
((SpanLabel)body).setTextUIID("PostStyleText");
body.setUIID(style);
NewsfeedContainer
There is however, one additional enhancement I added to the createNewsItem method to support the styled posts which up until now were ignored. I save the result of
the if statement here to make the following code shorter/simpler
15. Component body;
String style = null;
if(p.styling.get() != null && !p.styling.get().equals("Label")) {
style = p.styling.get();
}
if(p.content.get().indexOf('<') > -1) {
if(style != null) {
body = new RichTextView(p.content.get(), "PostStyleText");
((RichTextView)body).setAlignment(CENTER);
body.setUIID(style);
} else {
body = new RichTextView(p.content.get());
body.setUIID("HalfPaddedContainer");
}
} else {
body = new SpanLabel(p.content.get());
if(style != null) {
((SpanLabel)body).setTextUIID("PostStyleText");
body.setUIID(style);
} else {
body.setUIID("HalfPaddedContainer");
}
}
CheckBox like = CheckBox.createToggle("Like");
like.setUIID("CleanButton");
NewsfeedContainer
RichTextView now accepts a UIID for the content, I'll go into that code soon. The body is given the background style UIID.
16. Component body;
String style = null;
if(p.styling.get() != null && !p.styling.get().equals("Label")) {
style = p.styling.get();
}
if(p.content.get().indexOf('<') > -1) {
if(style != null) {
body = new RichTextView(p.content.get(), "PostStyleText");
((RichTextView)body).setAlignment(CENTER);
body.setUIID(style);
} else {
body = new RichTextView(p.content.get());
body.setUIID("HalfPaddedContainer");
}
} else {
body = new SpanLabel(p.content.get());
if(style != null) {
((SpanLabel)body).setTextUIID("PostStyleText");
body.setUIID(style);
} else {
body.setUIID("HalfPaddedContainer");
}
}
CheckBox like = CheckBox.createToggle("Like");
like.setUIID("CleanButton");
NewsfeedContainer
The same is true in the `SpanLabel` version of the code...
17. public class RichTextView extends Container {
private String text;
private float fontSize = 2.6f;
private EventDispatcher listeners = new EventDispatcher();
private Font currentFont;
private int currentColor = 0;
private String currentLink;
private Style lastCmp;
private Font defaultFont;
private Font boldFont;
private Font italicFont;
private int sizeOfSpace;
public RichTextView() {
init(null);
}
public RichTextView(String text, String uiid) {
init(uiid);
setText(text);
}
RichTextView
This leads us directly to the changes in RichTextView.
Because of its nature RichTextView is a bit of a hack without much support for styling. I wanted to give it some generic styling support for the rich posts so this isn't a
huge change but it helps…
First I had to make fontSize non-final as this will change due to styling:
18. private Font defaultFont;
private Font boldFont;
private Font italicFont;
private int sizeOfSpace;
public RichTextView() {
init(null);
}
public RichTextView(String text, String uiid) {
init(uiid);
setText(text);
}
public RichTextView(String text) {
init(null);
setText(text);
}
private void init(String uiid) {
boldFont = Font.createTrueTypeFont(NATIVE_MAIN_BOLD, fontSize);
italicFont = Font.createTrueTypeFont(NATIVE_ITALIC_LIGHT, fontSize);
if(uiid == null) {
defaultFont = Font.createTrueTypeFont(NATIVE_MAIN_LIGHT,
fontSize);
} else {
RichTextView
The next step is simply a change to the constructors and init method. Init now accepts an optional UIID which defaults to null.
19. private Font defaultFont;
private Font boldFont;
private Font italicFont;
private int sizeOfSpace;
public RichTextView() {
init(null);
}
public RichTextView(String text, String uiid) {
init(uiid);
setText(text);
}
public RichTextView(String text) {
init(null);
setText(text);
}
private void init(String uiid) {
boldFont = Font.createTrueTypeFont(NATIVE_MAIN_BOLD, fontSize);
italicFont = Font.createTrueTypeFont(NATIVE_ITALIC_LIGHT, fontSize);
if(uiid == null) {
defaultFont = Font.createTrueTypeFont(NATIVE_MAIN_LIGHT,
fontSize);
} else {
RichTextView
We set this UIID in this new constructor but leave it as null in the other cases
20. }
public RichTextView(String text) {
init(null);
setText(text);
}
private void init(String uiid) {
boldFont = Font.createTrueTypeFont(NATIVE_MAIN_BOLD, fontSize);
italicFont = Font.createTrueTypeFont(NATIVE_ITALIC_LIGHT, fontSize);
if(uiid == null) {
defaultFont = Font.createTrueTypeFont(NATIVE_MAIN_LIGHT,
fontSize);
} else {
Style s = UIManager.getInstance().getComponentStyle(uiid);
defaultFont = s.getFont();
boldFont = boldFont.derive(defaultFont.getHeight(),
Font.STYLE_BOLD);
italicFont = italicFont.derive(defaultFont.getHeight(),
Font.STYLE_ITALIC);
}
sizeOfSpace = defaultFont.charWidth(' ');
currentFont = defaultFont;
}
public void setAlignment(int align) {
RichTextView
If the UIID isn't set everything acts like it did before...
21. }
public RichTextView(String text) {
init(null);
setText(text);
}
private void init(String uiid) {
boldFont = Font.createTrueTypeFont(NATIVE_MAIN_BOLD, fontSize);
italicFont = Font.createTrueTypeFont(NATIVE_ITALIC_LIGHT, fontSize);
if(uiid == null) {
defaultFont = Font.createTrueTypeFont(NATIVE_MAIN_LIGHT,
fontSize);
} else {
Style s = UIManager.getInstance().getComponentStyle(uiid);
defaultFont = s.getFont();
boldFont = boldFont.derive(defaultFont.getHeight(),
Font.STYLE_BOLD);
italicFont = italicFont.derive(defaultFont.getHeight(),
Font.STYLE_ITALIC);
}
sizeOfSpace = defaultFont.charWidth(' ');
currentFont = defaultFont;
}
public void setAlignment(int align) {
RichTextView
Otherwise we get the default font from the style then resize the bold/italic fonts to match its size. That's it, we can now style the default font of the rich text view.