Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Ā
Creating a Facebook Clone - Part XLI - Transcript.pdf
1. Creating a Facebook Clone - Part XLI
We are now ready for the NewPostForm changes
2. public class NewPostForm extends Form {
private static final String[] POST_STYLES = {
"Label", "PostStyleHearts", "PostStyleHands", "PostStyleBlack",
"PostStyleRed", "PostStylePurple" };
private TextArea post = new TextArea(3, 80);
private String postStyleValue;
private String attachment;
private String mime;
private Button postButton;
private NewPostForm() {
super("Create Post", new BorderLayout());
}
private void initUI(Container postStyles) {
Form current = getCurrentForm();
getToolbar().setBackCommand("Cancel",
Toolbar.BackCommandPolicy.
WHEN_USES_TITLE_OTHERWISE_ARROW,
e -> current.showBack());
Command c = getToolbar().addMaterialCommandToRightBar("",
FontImage.MATERIAL_DONE, e -> post(current));
postButton = getToolbar().findCommandComponent(c);
NewPostForm
Up until now the changes were simple but in this class we have a bit of work to do. Lets start with the top level changes.
Currently we support one attachment, this field represents the attachment id
3. public class NewPostForm extends Form {
private static final String[] POST_STYLES = {
"Label", "PostStyleHearts", "PostStyleHands", "PostStyleBlack",
"PostStyleRed", "PostStylePurple" };
private TextArea post = new TextArea(3, 80);
private String postStyleValue;
private String attachment;
private String mime;
private Button postButton;
private NewPostForm() {
super("Create Post", new BorderLayout());
}
private void initUI(Container postStyles) {
Form current = getCurrentForm();
getToolbar().setBackCommand("Cancel",
Toolbar.BackCommandPolicy.
WHEN_USES_TITLE_OTHERWISE_ARROW,
e -> current.showBack());
Command c = getToolbar().addMaterialCommandToRightBar("",
FontImage.MATERIAL_DONE, e -> post(current));
postButton = getToolbar().findCommandComponent(c);
NewPostForm
This is the attachment mime type if applicable
4. public class NewPostForm extends Form {
private static final String[] POST_STYLES = {
"Label", "PostStyleHearts", "PostStyleHands", "PostStyleBlack",
"PostStyleRed", "PostStylePurple" };
private TextArea post = new TextArea(3, 80);
private String postStyleValue;
private String attachment;
private String mime;
private Button postButton;
private NewPostForm() {
super("Create Post", new BorderLayout());
}
private void initUI(Container postStyles) {
Form current = getCurrentForm();
getToolbar().setBackCommand("Cancel",
Toolbar.BackCommandPolicy.
WHEN_USES_TITLE_OTHERWISE_ARROW,
e -> current.showBack());
Command c = getToolbar().addMaterialCommandToRightBar("",
FontImage.MATERIAL_DONE, e -> post(current));
postButton = getToolbar().findCommandComponent(c);
NewPostForm
This is the button for sending a post we need to disable this button until the attachment is uploaded
5. public class NewPostForm extends Form {
private static final String[] POST_STYLES = {
"Label", "PostStyleHearts", "PostStyleHands", "PostStyleBlack",
"PostStyleRed", "PostStylePurple" };
private TextArea post = new TextArea(3, 80);
private String postStyleValue;
private String attachment;
private String mime;
private Button postButton;
private NewPostForm() {
super("Create Post", new BorderLayout());
}
private void initUI(Container postStyles) {
Form current = getCurrentForm();
getToolbar().setBackCommand("Cancel",
Toolbar.BackCommandPolicy.
WHEN_USES_TITLE_OTHERWISE_ARROW,
e -> current.showBack());
Command c = getToolbar().addMaterialCommandToRightBar("",
FontImage.MATERIAL_DONE, e -> post(current));
postButton = getToolbar().findCommandComponent(c);
NewPostForm
The constructor is now private as the class is instantiated via factory methods. Also notice we don't invoke initUI() in the constructor anymore!āØ
The first big change is the initUI method which up until now created the same UI and was bound in the constructor. This will no longer work as the UI with an attachment
will be slightly diļ¬erent. This code is still there it just moved to the factory method.
6. userSettings.add(CENTER,
BoxLayout.encloseY(
new Label(me.fullName(), "MultiLine1"),
FlowLayout.encloseIn(friends)));
add(NORTH, userSettings);
post.setUIID("Label");
post.setGrowByContent(false);
Component l;
if(postStyles != null) {
l = LayeredLayout.encloseIn(
BorderLayout.north(post), postStyles);
} else {
l = post;
}
add(CENTER, l);
setEditOnShow(post);
}
public static NewPostForm createPost() {
NewPostForm n = new NewPostForm();
Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
NewPostForm
Hereā¦
7. userSettings.add(CENTER,
BoxLayout.encloseY(
new Label(me.fullName(), "MultiLine1"),
FlowLayout.encloseIn(friends)));
add(NORTH, userSettings);
post.setUIID("Label");
post.setGrowByContent(false);
Component l;
if(postStyles != null) {
l = LayeredLayout.encloseIn(
BorderLayout.north(post), postStyles);
} else {
l = post;
}
add(CENTER, l);
setEditOnShow(post);
}
public static NewPostForm createPost() {
NewPostForm n = new NewPostForm();
Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
NewPostForm
The postStyles code happened at the end of initUI originally but due to logistics we need it here.
This allows the image & video versions to avoid the styles bar at the bottom of the UI. Notice that those don't exist in the native Facebook app either.
8. private Button postButton;
private NewPostForm() {
super("Create Post", new BorderLayout());
}
private void initUI(Container postStyles) {
Form current = getCurrentForm();
getToolbar().setBackCommand("Cancel",
Toolbar.BackCommandPolicy.
WHEN_USES_TITLE_OTHERWISE_ARROW,
e -> current.showBack());
Command c = getToolbar().addMaterialCommandToRightBar("",
FontImage.MATERIAL_DONE, e -> post(current));
postButton = getToolbar().findCommandComponent(c);
User me = ServerAPI.me();
Container userSettings = BorderLayout.west(
new Label(me.getAvatar(6.5f), "HalfPaddedContainer"));
Button friends = new Button("Friends", "FriendCombo");
FontImage.setMaterialIcon(friends, FontImage.MATERIAL_PEOPLE);
userSettings.add(CENTER,
BoxLayout.encloseY(
new Label(me.fullName(), "MultiLine1"),
FlowLayout.encloseIn(friends)));
add(NORTH, userSettings);
post.setUIID("Label");
post.setGrowByContent(false);
NewPostForm
Which brings us back to the initUI method
9. private Button postButton;
private NewPostForm() {
super("Create Post", new BorderLayout());
}
private void initUI(Container postStyles) {
Form current = getCurrentForm();
getToolbar().setBackCommand("Cancel",
Toolbar.BackCommandPolicy.
WHEN_USES_TITLE_OTHERWISE_ARROW,
e -> current.showBack());
Command c = getToolbar().addMaterialCommandToRightBar("",
FontImage.MATERIAL_DONE, e -> post(current));
postButton = getToolbar().findCommandComponent(c);
User me = ServerAPI.me();
Container userSettings = BorderLayout.west(
new Label(me.getAvatar(6.5f), "HalfPaddedContainer"));
Button friends = new Button("Friends", "FriendCombo");
FontImage.setMaterialIcon(friends, FontImage.MATERIAL_PEOPLE);
userSettings.add(CENTER,
BoxLayout.encloseY(
new Label(me.fullName(), "MultiLine1"),
FlowLayout.encloseIn(friends)));
add(NORTH, userSettings);
post.setUIID("Label");
post.setGrowByContent(false);
NewPostForm
This is the button for posting from the toolbar, we can later disable this button. We can find the button for toolbar commands to get low level control
10. Command c = getToolbar().addMaterialCommandToRightBar("",
FontImage.MATERIAL_DONE, e -> post(current));
postButton = getToolbar().findCommandComponent(c);
User me = ServerAPI.me();
Container userSettings = BorderLayout.west(
new Label(me.getAvatar(6.5f), "HalfPaddedContainer"));
Button friends = new Button("Friends", "FriendCombo");
FontImage.setMaterialIcon(friends, FontImage.MATERIAL_PEOPLE);
userSettings.add(CENTER,
BoxLayout.encloseY(
new Label(me.fullName(), "MultiLine1"),
FlowLayout.encloseIn(friends)));
add(NORTH, userSettings);
post.setUIID("Label");
post.setGrowByContent(false);
Component l;
if(postStyles != null) {
l = LayeredLayout.encloseIn(
BorderLayout.north(post), postStyles);
} else {
l = post;
}
add(CENTER, l);
setEditOnShow(post);
}
NewPostForm
This code will behave exactly the same as it did before if postStyles has a value and will just add the post to center if it doesn't
11. Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
n.mime = "image/jpeg";
ScaleImageLabel i = new ScaleImageLabel(img) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
Slider s = n.upload(p);
n.add(SOUTH, LayeredLayout.encloseIn(
i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
NewPostForm
Those are somewhat nuanced changes that expose components we can work with. Lets see how this allows image uploads in the main method. āØ
This is a factory method for an image post.
12. Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
n.mime = "image/jpeg";
ScaleImageLabel i = new ScaleImageLabel(img) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
Slider s = n.upload(p);
n.add(SOUTH, LayeredLayout.encloseIn(
i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
NewPostForm
The style bar in the bottom isn't included
13. Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
n.mime = "image/jpeg";
ScaleImageLabel i = new ScaleImageLabel(img) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
Slider s = n.upload(p);
n.add(SOUTH, LayeredLayout.encloseIn(
i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
NewPostForm
We set the mime type here, but notice we don't know the media ID yet so the attachment value isn't set!
14. Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
n.mime = "image/jpeg";
ScaleImageLabel i = new ScaleImageLabel(img) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
Slider s = n.upload(p);
n.add(SOUTH, LayeredLayout.encloseIn(
i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
NewPostForm
The image will take up half the screen height, this is better than using an API like setPreferredSize because it will still work if the device is rotated
15. Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
n.mime = "image/jpeg";
ScaleImageLabel i = new ScaleImageLabel(img) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
Slider s = n.upload(p);
n.add(SOUTH, LayeredLayout.encloseIn(
i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
NewPostForm
Here we upload the media to the server and return a Slider component to track the upload progress
16. Container postStyles = n.createPostStyles(n.post);
n.initUI(BorderLayout.south(postStyles));
return n;
}
public static NewPostForm createImagePost(EncodedImage img,
ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
n.mime = "image/jpeg";
ScaleImageLabel i = new ScaleImageLabel(img) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
Slider s = n.upload(p);
n.add(SOUTH, LayeredLayout.encloseIn(
i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
NewPostForm
We place the image on the bottom of the form and the slider progress on top of it further down.āØ
Before we go to the logical next step in the upload method I'd like to point out a big missing piece: delete. If we close this post now the image would still be in the
servers and no one would know... āØ
That's a flaw I left in place due to time constraints. There are several potential fixes, probably the best one would be a backend server process that deletes media that
has no references.
We can also add a delete WebService but that wouldn't work perfectly for cases of app crashes etc.
17. }
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
return n;
}
private Slider upload(ImagePicker p) {
postButton.setEnabled(false);
Slider s = new Slider();
MultipartRequest m = p.upload(e -> {
attachment = e;
postButton.setEnabled(true);
});
SliderBridge.bindProgress(m, s);
return s;
}
NewPostForm
Iāll come back to the video post code soon. For now I'll skip ahead to the upload method.
19. }
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
return n;
}
private Slider upload(ImagePicker p) {
postButton.setEnabled(false);
Slider s = new Slider();
MultipartRequest m = p.upload(e -> {
attachment = e;
postButton.setEnabled(true);
});
SliderBridge.bindProgress(m, s);
return s;
}
NewPostForm
Once upload finished we enable the post button, notice we save the attachment value too which we will need later
20. }
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
return n;
}
private Slider upload(ImagePicker p) {
postButton.setEnabled(false);
Slider s = new Slider();
MultipartRequest m = p.upload(e -> {
attachment = e;
postButton.setEnabled(true);
});
SliderBridge.bindProgress(m, s);
return s;
}
NewPostForm
You can bind a connection request to a slider using this utility class builtin to Codename One.
Every stream in Codename One is observable which means we can get update events on any IO operation seamlessly. Utilities like SliderBridge take this to the next
logical step by binding the progress events from NetworkManager to a Slider.
21. attachment = e;
postButton.setEnabled(true);
});
SliderBridge.bindProgress(m, s);
return s;
}
private void post(Form previousForm) {
Dialog dlg = new InfiniteProgress().showInifiniteBlocking();
Post p = new Post().
content.set(post.getText()).
visibility.set("public").
styling.set(postStyleValue);
if(attachment != null) {
p.attachments.put(attachment, mime);
}
if(!ServerAPI.post(p)) {
dlg.dispose();
ToastBar.showErrorMessage("Error posting to server");
return;
}
previousForm.showBack();
}
private Container createPostStyles(TextArea post) {
Container postStyles = new Container(BoxLayout.x());
NewPostForm
With this lets go directly to the changes in post to support attachmentsā¦āØ
This code had to move outside of the if statement even though it hasn't changed
22. attachment = e;
postButton.setEnabled(true);
});
SliderBridge.bindProgress(m, s);
return s;
}
private void post(Form previousForm) {
Dialog dlg = new InfiniteProgress().showInifiniteBlocking();
Post p = new Post().
content.set(post.getText()).
visibility.set("public").
styling.set(postStyleValue);
if(attachment != null) {
p.attachments.put(attachment, mime);
}
if(!ServerAPI.post(p)) {
dlg.dispose();
ToastBar.showErrorMessage("Error posting to server");
return;
}
previousForm.showBack();
}
private Container createPostStyles(TextArea post) {
Container postStyles = new Container(BoxLayout.x());
NewPostForm
If we have an attachment we add it to the post object
23. attachment = e;
postButton.setEnabled(true);
});
SliderBridge.bindProgress(m, s);
return s;
}
private void post(Form previousForm) {
Dialog dlg = new InfiniteProgress().showInifiniteBlocking();
Post p = new Post().
content.set(post.getText()).
visibility.set("public").
styling.set(postStyleValue);
if(attachment != null) {
p.attachments.put(attachment, mime);
}
if(!ServerAPI.post(p)) {
dlg.dispose();
ToastBar.showErrorMessage("Error posting to server");
return;
}
previousForm.showBack();
}
private Container createPostStyles(TextArea post) {
Container postStyles = new Container(BoxLayout.x());
NewPostForm
After this line the rest of the code is identical. With that image posting should work although we still need to update the callers to use the factory methods instead of the
constructor.
24. i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
Slider s = n.upload(p);
n.mime = "video/mp4";
Container videoContainer = new Container(new LayeredLayout()) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
NewPostForm
We have one final change to the NewPostForm, the createVideoPost method
25. i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
Slider s = n.upload(p);
n.mime = "video/mp4";
Container videoContainer = new Container(new LayeredLayout()) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
NewPostForm
Most of this looks just like the createImagePost method with minor changes such as mp4 instead of jpeg etc.
26. i, BorderLayout.south(s)));
return n;
}
public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
Slider s = n.upload(p);
n.mime = "video/mp4";
Container videoContainer = new Container(new LayeredLayout()) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
NewPostForm
We can't override the preferred size of the video component but we can place it in a Container where we do override the size
27. public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
Slider s = n.upload(p);
n.mime = "video/mp4";
Container videoContainer = new Container(new LayeredLayout()) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
return n;
}
NewPostForm
We need that container anyway so we can place a progress indicator on top of that to show the upload progress
28. public static NewPostForm createVideoPost(Media m, ImagePicker p) {
NewPostForm n = new NewPostForm();
n.initUI(null);
Slider s = n.upload(p);
n.mime = "video/mp4";
Container videoContainer = new Container(new LayeredLayout()) {
@Override
protected Dimension calcPreferredSize() {
return new Dimension(getDisplayWidth(),
getDisplayHeight() / 2);
}
};
videoContainer.add(m.getVideoComponent());
videoContainer.add(BorderLayout.south(s));
n.add(SOUTH, videoContainer);
n.addShowListener(e -> {
m.play();
m.setVolume(0);
m.setTime(Math.min(m.getDuration() / 2, 1000));
m.pause();
});
return n;
}
NewPostForm
This shows a frame of the video 1 second into playback. Notice I invoke play() and only then try seeking, without doing that the behavior is undefined for some features
such as seek.
With that the NewPostForm is done we just need to wire it in.