3. public class ImagePicker {
private String fileName;
private EncodedImage img;
private int mode = GALLERY_IMAGE;
public ImagePicker() {
}
public ImagePicker(int mode) {
this.mode = mode;
}
public void pickImage(SuccessCallback<EncodedImage> onPick) {
Display.getInstance().openGallery(e -> {
if(e == null) {
return;
}
String s = (String)e.getSource();
if(s != null) {
try(InputStream i = openFileInputStream(s);) {
img = EncodedImage.create(i, (int)getFileLength(s));
fileName = s;
onPick.onSucess(img);
} catch(IOException err) {
ImagePicker
I've encapsulated image picking in the ImagePicker class, this is convenient as we might want to make it more elaborate in the future. At the moment it simply picks from
the device gallery.
We don't need a class in terms of implementation. It's there to encapsulate the meta-data of the image in a single operation
4. public class ImagePicker {
private String fileName;
private EncodedImage img;
private int mode = GALLERY_IMAGE;
public ImagePicker() {
}
public ImagePicker(int mode) {
this.mode = mode;
}
public void pickImage(SuccessCallback<EncodedImage> onPick) {
Display.getInstance().openGallery(e -> {
if(e == null) {
return;
}
String s = (String)e.getSource();
if(s != null) {
try(InputStream i = openFileInputStream(s);) {
img = EncodedImage.create(i, (int)getFileLength(s));
fileName = s;
onPick.onSucess(img);
} catch(IOException err) {
ImagePicker
The filename is useful for the upload process as we maintain that meta-data in the server for reference
5. public class ImagePicker {
private String fileName;
private EncodedImage img;
private int mode = GALLERY_IMAGE;
public ImagePicker() {
}
public ImagePicker(int mode) {
this.mode = mode;
}
public void pickImage(SuccessCallback<EncodedImage> onPick) {
Display.getInstance().openGallery(e -> {
if(e == null) {
return;
}
String s = (String)e.getSource();
if(s != null) {
try(InputStream i = openFileInputStream(s);) {
img = EncodedImage.create(i, (int)getFileLength(s));
fileName = s;
onPick.onSucess(img);
} catch(IOException err) {
ImagePicker
openGallery launches the OS native picker code in this case using image mode
6. private int mode = GALLERY_IMAGE;
public ImagePicker() {
}
public ImagePicker(int mode) {
this.mode = mode;
}
public void pickImage(SuccessCallback<EncodedImage> onPick) {
Display.getInstance().openGallery(e -> {
if(e == null) {
return;
}
String s = (String)e.getSource();
if(s != null) {
try(InputStream i = openFileInputStream(s);) {
img = EncodedImage.create(i, (int)getFileLength(s));
fileName = s;
onPick.onSucess(img);
} catch(IOException err) {
Log.e(err);
}
}
}, mode);
}
public void upload(SuccessCallback<String> mediaId) {
ServerAPI.uploadMedia("image/jpeg", "photo", "public", fileName,
ImagePicker
We keep the fileName & the image object
7. this.mode = mode;
}
public void pickImage(SuccessCallback<EncodedImage> onPick) {
Display.getInstance().openGallery(e -> {
if(e == null) {
return;
}
String s = (String)e.getSource();
if(s != null) {
try(InputStream i = openFileInputStream(s);) {
img = EncodedImage.create(i, (int)getFileLength(s));
fileName = s;
onPick.onSucess(img);
} catch(IOException err) {
Log.e(err);
}
}
}, mode);
}
public void upload(SuccessCallback<String> mediaId) {
ServerAPI.uploadMedia("image/jpeg", "photo", "public", fileName,
img.getImageData(), mediaId);
}
}
ImagePicker
This class encapsulates the upload feature too so the filename can remain encapsulated. That's pretty simple, we could possibly do more by offering an option to launch
the camera etc.
8. public class SettingsForm extends Form {
private Label cover = new Label(" ", "LoginTitle");
private Label avatar = new Label("", "LabelFrame");
private Button changeCover =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
private Button changeAvatar =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
public SettingsForm() {
super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
SettingsForm
The settings UI is encapsulated in the SettingsForm class.
9. public class SettingsForm extends Form {
private Label cover = new Label(" ", "LoginTitle");
private Label avatar = new Label("", "LabelFrame");
private Button changeCover =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
private Button changeAvatar =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
public SettingsForm() {
super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
SettingsForm
cover & avatar represent the two images in the settings UI, the background cover image and avatar image
10. public class SettingsForm extends Form {
private Label cover = new Label(" ", "LoginTitle");
private Label avatar = new Label("", "LabelFrame");
private Button changeCover =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
private Button changeAvatar =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
public SettingsForm() {
super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
SettingsForm
The two change fields represent the camera change buttons that allow us to replace the avatar images
11. new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
private Button changeAvatar =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
public SettingsForm() {
super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
changeCover.addActionListener(e -> pickCover(cover));
Container coverContainer = LayeredLayout.encloseIn(
cover,
FlowLayout.encloseRightBottom(changeCover)
);
coverContainer.setUIID("SettingsMargin");
Container avatarContainer = LayeredLayout.encloseIn(
avatar,
SettingsForm
Next lets explore the constructor code, it's mostly standard and creates the main settings UI for replacing the images. Here we bind the back button behavior
12. new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
private Button changeAvatar =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
public SettingsForm() {
super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
changeCover.addActionListener(e -> pickCover(cover));
Container coverContainer = LayeredLayout.encloseIn(
cover,
FlowLayout.encloseRightBottom(changeCover)
);
coverContainer.setUIID("SettingsMargin");
Container avatarContainer = LayeredLayout.encloseIn(
avatar,
SettingsForm
The current avatar is set into the avatar label
13. new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
private Button changeAvatar =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
public SettingsForm() {
super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
changeCover.addActionListener(e -> pickCover(cover));
Container coverContainer = LayeredLayout.encloseIn(
cover,
FlowLayout.encloseRightBottom(changeCover)
);
coverContainer.setUIID("SettingsMargin");
Container avatarContainer = LayeredLayout.encloseIn(
avatar,
SettingsForm
If a cover image is set we fetch the image asynchronously
14. new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
private Button changeAvatar =
new Button(MATERIAL_CAMERA_ALT, "CameraLabel");
public SettingsForm() {
super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
changeCover.addActionListener(e -> pickCover(cover));
Container coverContainer = LayeredLayout.encloseIn(
cover,
FlowLayout.encloseRightBottom(changeCover)
);
coverContainer.setUIID("SettingsMargin");
Container avatarContainer = LayeredLayout.encloseIn(
avatar,
SettingsForm
Both image buttons delegate to picker methods so we can replace that logic in the future
15. super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
changeCover.addActionListener(e -> pickCover(cover));
Container coverContainer = LayeredLayout.encloseIn(
cover,
FlowLayout.encloseRightBottom(changeCover)
);
coverContainer.setUIID("SettingsMargin");
Container avatarContainer = LayeredLayout.encloseIn(
avatar,
FlowLayout.encloseRightBottom(changeAvatar)
);
add(LayeredLayout.encloseIn(
coverContainer,
FlowLayout.encloseCenterBottom(avatarContainer)
SettingsForm
The cover label places the change button on top using a LayeredLayout & wraps the button in a flow layout so it will align to the bottom right
16. super("", BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("Back", e -> previous.showBack());
User me = ServerAPI.me();
avatar.setIcon(me.getAvatar(12));
if(me.cover.get() != null) {
me.fetchCoverImage(i -> {
cover.getAllStyles().setBgImage(i);
repaint();
});
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
changeCover.addActionListener(e -> pickCover(cover));
Container coverContainer = LayeredLayout.encloseIn(
cover,
FlowLayout.encloseRightBottom(changeCover)
);
coverContainer.setUIID("SettingsMargin");
Container avatarContainer = LayeredLayout.encloseIn(
avatar,
FlowLayout.encloseRightBottom(changeAvatar)
);
add(LayeredLayout.encloseIn(
coverContainer,
FlowLayout.encloseCenterBottom(avatarContainer)
SettingsForm
We do the exact same thing with the avatar image and its picker button
17. });
}
changeAvatar.addActionListener(e -> pickAvatar(avatar));
changeCover.addActionListener(e -> pickCover(cover));
Container coverContainer = LayeredLayout.encloseIn(
cover,
FlowLayout.encloseRightBottom(changeCover)
);
coverContainer.setUIID("SettingsMargin");
Container avatarContainer = LayeredLayout.encloseIn(
avatar,
FlowLayout.encloseRightBottom(changeAvatar)
);
add(LayeredLayout.encloseIn(
coverContainer,
FlowLayout.encloseCenterBottom(avatarContainer)
));
add(new Label(me.fullName(), "CenterLargeThinLabel"));
add(createButtonBar());
}
private Container createButtonBar() {
Button activity = new Button(MATERIAL_HISTORY, "CleanButton");
Button settings = new Button(MATERIAL_SETTINGS, "CleanButton");
Button viewAs = new Button(MATERIAL_ACCOUNT_CIRCLE, "CleanButton");
SettingsForm
We then wrap both of those LayeredLayout’s in another layered layout and the avatar in a FlowLayout to align it to the bottom center region.
The LayeredLayout usage raises the obvious question of why not use one layered layout instead of three?
That would be hard to accomplish with the picker button. We would have a hard time aligning the picker button of the avatar to the bottom right corner since the parent
layout would be too big.
18. FlowLayout.encloseCenterBottom(avatarContainer)
));
add(new Label(me.fullName(), "CenterLargeThinLabel"));
add(createButtonBar());
}
private Container createButtonBar() {
Button activity = new Button(MATERIAL_HISTORY, "CleanButton");
Button settings = new Button(MATERIAL_SETTINGS, "CleanButton");
Button viewAs = new Button(MATERIAL_ACCOUNT_CIRCLE, "CleanButton");
Button more = new Button(MATERIAL_MORE_HORIZ, "CleanButton");
settings.addActionListener(e ->
showUserEditForm(ServerAPI.me()));
return GridLayout.encloseIn(4, activity, settings, viewAs, more);
}
private void pickAvatar(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().avatar.set(mediaId);
ServerAPI.setAvatar(mediaId);
Image temp = img.fill(l.getIcon().getWidth(),
l.getIcon().getHeight());
temp = temp.applyMask(ServerAPI.me().
SettingsForm
Next up are the buttons we create at the bottom of the UI as mentioned at the end of the constructor.
The button bar are just 4 buttons in a horizontal grid, we only map the settings to functionality of full editing in showUserEditForm
19. Button viewAs = new Button(MATERIAL_ACCOUNT_CIRCLE, "CleanButton");
Button more = new Button(MATERIAL_MORE_HORIZ, "CleanButton");
settings.addActionListener(e ->
showUserEditForm(ServerAPI.me()));
return GridLayout.encloseIn(4, activity, settings, viewAs, more);
}
private void pickAvatar(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().avatar.set(mediaId);
ServerAPI.setAvatar(mediaId);
Image temp = img.fill(l.getIcon().getWidth(),
l.getIcon().getHeight());
temp = temp.applyMask(ServerAPI.me().
getAvatarMask(temp.getWidth()).createMask());
l.setIcon(temp);
});
});
}
private void pickCover(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
SettingsForm
ImagePicker does most of the heavy lifting here we launch it and get an image object back if it proceeded
20. Button viewAs = new Button(MATERIAL_ACCOUNT_CIRCLE, "CleanButton");
Button more = new Button(MATERIAL_MORE_HORIZ, "CleanButton");
settings.addActionListener(e ->
showUserEditForm(ServerAPI.me()));
return GridLayout.encloseIn(4, activity, settings, viewAs, more);
}
private void pickAvatar(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().avatar.set(mediaId);
ServerAPI.setAvatar(mediaId);
Image temp = img.fill(l.getIcon().getWidth(),
l.getIcon().getHeight());
temp = temp.applyMask(ServerAPI.me().
getAvatarMask(temp.getWidth()).createMask());
l.setIcon(temp);
});
});
}
private void pickCover(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
SettingsForm
We upload the image and get a callback with the media id value
21. Button viewAs = new Button(MATERIAL_ACCOUNT_CIRCLE, "CleanButton");
Button more = new Button(MATERIAL_MORE_HORIZ, "CleanButton");
settings.addActionListener(e ->
showUserEditForm(ServerAPI.me()));
return GridLayout.encloseIn(4, activity, settings, viewAs, more);
}
private void pickAvatar(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().avatar.set(mediaId);
ServerAPI.setAvatar(mediaId);
Image temp = img.fill(l.getIcon().getWidth(),
l.getIcon().getHeight());
temp = temp.applyMask(ServerAPI.me().
getAvatarMask(temp.getWidth()).createMask());
l.setIcon(temp);
});
});
}
private void pickCover(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
SettingsForm
The avatar is set both locally & remotely so we don't need a server refresh call
22. Button viewAs = new Button(MATERIAL_ACCOUNT_CIRCLE, "CleanButton");
Button more = new Button(MATERIAL_MORE_HORIZ, "CleanButton");
settings.addActionListener(e ->
showUserEditForm(ServerAPI.me()));
return GridLayout.encloseIn(4, activity, settings, viewAs, more);
}
private void pickAvatar(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().avatar.set(mediaId);
ServerAPI.setAvatar(mediaId);
Image temp = img.fill(l.getIcon().getWidth(),
l.getIcon().getHeight());
temp = temp.applyMask(ServerAPI.me().
getAvatarMask(temp.getWidth()).createMask());
l.setIcon(temp);
});
});
}
private void pickCover(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
SettingsForm
We do refresh the icon though and make sure to shape it as it was before
23. temp = temp.applyMask(ServerAPI.me().
getAvatarMask(temp.getWidth()).createMask());
l.setIcon(temp);
});
});
}
private void pickCover(Label l) {
ImagePicker p = new ImagePicker();
p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().cover.set(mediaId);
ServerAPI.setCover(mediaId);
l.getAllStyles().setBgImage(img);
});
});
}
@Override
protected void initGlobalToolbar() {
Toolbar tb = new Toolbar(true);
tb.setUIID("Container");
setToolbar(tb);
}
SettingsForm
Cover picking is very similar but has simpler code as we don't need to shape the resulting image & have a simpler API
24. p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().cover.set(mediaId);
ServerAPI.setCover(mediaId);
l.getAllStyles().setBgImage(img);
});
});
}
@Override
protected void initGlobalToolbar() {
Toolbar tb = new Toolbar(true);
tb.setUIID("Container");
setToolbar(tb);
}
private void showUserEditForm(User me) {
InstantUI iu = new InstantUI();
iu.excludeProperties(me.authtoken, me.avatar, me.cover,
me.friendRequests, me.friends, me.peopleYouMayKnow, me.id,
me.password, me.birthday);
iu.setMultiChoiceLabels(me.gender, "Male", "Female", "Other");
iu.setMultiChoiceValues(me.gender, "Male", "Female", "Other");
Container cnt = iu.createEditUI(me, true);
cnt.setUIID("PaddedContainer");
SettingsForm
Next we override this method to have a custom toolbar
25. p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().cover.set(mediaId);
ServerAPI.setCover(mediaId);
l.getAllStyles().setBgImage(img);
});
});
}
@Override
protected void initGlobalToolbar() {
Toolbar tb = new Toolbar(true);
tb.setUIID("Container");
setToolbar(tb);
}
private void showUserEditForm(User me) {
InstantUI iu = new InstantUI();
iu.excludeProperties(me.authtoken, me.avatar, me.cover,
me.friendRequests, me.friends, me.peopleYouMayKnow, me.id,
me.password, me.birthday);
iu.setMultiChoiceLabels(me.gender, "Male", "Female", "Other");
iu.setMultiChoiceValues(me.gender, "Male", "Female", "Other");
Container cnt = iu.createEditUI(me, true);
cnt.setUIID("PaddedContainer");
SettingsForm
The toolbar is created using the "layered" mode (the true argument) which makes the content run under it
26. p.pickImage(img -> {
p.upload(mediaId -> {
ServerAPI.me().cover.set(mediaId);
ServerAPI.setCover(mediaId);
l.getAllStyles().setBgImage(img);
});
});
}
@Override
protected void initGlobalToolbar() {
Toolbar tb = new Toolbar(true);
tb.setUIID("Container");
setToolbar(tb);
}
private void showUserEditForm(User me) {
InstantUI iu = new InstantUI();
iu.excludeProperties(me.authtoken, me.avatar, me.cover,
me.friendRequests, me.friends, me.peopleYouMayKnow, me.id,
me.password, me.birthday);
iu.setMultiChoiceLabels(me.gender, "Male", "Female", "Other");
iu.setMultiChoiceValues(me.gender, "Male", "Female", "Other");
Container cnt = iu.createEditUI(me, true);
cnt.setUIID("PaddedContainer");
SettingsForm
We use the Container UIID for the `Toolbar` to make it transparent
27. Toolbar tb = new Toolbar(true);
tb.setUIID("Container");
setToolbar(tb);
}
private void showUserEditForm(User me) {
InstantUI iu = new InstantUI();
iu.excludeProperties(me.authtoken, me.avatar, me.cover,
me.friendRequests, me.friends, me.peopleYouMayKnow, me.id,
me.password, me.birthday);
iu.setMultiChoiceLabels(me.gender, "Male", "Female", "Other");
iu.setMultiChoiceValues(me.gender, "Male", "Female", "Other");
Container cnt = iu.createEditUI(me, true);
cnt.setUIID("PaddedContainer");
cnt.setScrollableY(true);
Form edit = new Form("Edit", new BorderLayout());
edit.add(BorderLayout.CENTER, cnt);
edit.getToolbar().setBackCommand("Back", e -> {
showBack();
UiBinding.unbind(me);
callSerially(() -> ServerAPI.update(me));
});
edit.show();
}
}
SettingsForm
Finally the last piece of code is the showUserEditForm(). Since there is still a lot to cover here I’ll continue in the next lesson