3. public class EnterMobileNumberForm extends Form {
public EnterMobileNumberForm() {
super(BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
add(new Label("Enter your mobile number", "FlagButton"));
CountryCodePicker countryCodeButton = new CountryCodePicker();
TextField phoneNumber = new TextField("", "050-123-4567", 40,
TextField.PHONENUMBER);
add(BorderLayout.centerEastWest(
phoneNumber,
null,
countryCodeButton));
Style ps = phoneNumber.getUnselectedStyle();
Style cs = countryCodeButton.getUnselectedStyle();
int pl = cs.getPaddingLeft(isRTL());
int pr = cs.getPaddingRight(isRTL());
countryCodeButton.getAllStyles().setPaddingUnit(Style.UNIT_TYPE_PIXELS);
countryCodeButton.getAllStyles().setPadding(ps.getPaddingTop(),
ps.getPaddingBottom(), pl, pr);
EnterMobileNumberForm
Let’s jump right into the code that makes that form…
We use standard back navigation since the toolbar is pretty standard here
4. public class EnterMobileNumberForm extends Form {
public EnterMobileNumberForm() {
super(BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
add(new Label("Enter your mobile number", "FlagButton"));
CountryCodePicker countryCodeButton = new CountryCodePicker();
TextField phoneNumber = new TextField("", "050-123-4567", 40,
TextField.PHONENUMBER);
add(BorderLayout.centerEastWest(
phoneNumber,
null,
countryCodeButton));
Style ps = phoneNumber.getUnselectedStyle();
Style cs = countryCodeButton.getUnselectedStyle();
int pl = cs.getPaddingLeft(isRTL());
int pr = cs.getPaddingRight(isRTL());
countryCodeButton.getAllStyles().setPaddingUnit(Style.UNIT_TYPE_PIXELS);
countryCodeButton.getAllStyles().setPadding(ps.getPaddingTop(),
ps.getPaddingBottom(), pl, pr);
EnterMobileNumberForm
The phone number text field is right next to the country code button. We place it in the center of a border layout so it will take up all available space
5. public class EnterMobileNumberForm extends Form {
public EnterMobileNumberForm() {
super(BoxLayout.y());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
add(new Label("Enter your mobile number", "FlagButton"));
CountryCodePicker countryCodeButton = new CountryCodePicker();
TextField phoneNumber = new TextField("", "050-123-4567", 40,
TextField.PHONENUMBER);
add(BorderLayout.centerEastWest(
phoneNumber,
null,
countryCodeButton));
Style ps = phoneNumber.getUnselectedStyle();
Style cs = countryCodeButton.getUnselectedStyle();
int pl = cs.getPaddingLeft(isRTL());
int pr = cs.getPaddingRight(isRTL());
countryCodeButton.getAllStyles().setPaddingUnit(Style.UNIT_TYPE_PIXELS);
countryCodeButton.getAllStyles().setPadding(ps.getPaddingTop(),
ps.getPaddingBottom(), pl, pr);
EnterMobileNumberForm
I want the padding on the text field and button to match so they align properly. Once paddings are set they are always in pixels so we need to change the style to use
pixels. I don't want to impact the left/right padding values so I extract them first and save them so I can restore them into the UI. I could technically create a separate
UIID to align both but I wanted to do this in the code so future changes to the theme won't break alignment
Just so you’ll get a sense of why this exists this screenshot shows side by side how this looks with and without the alignment code. Guess which is the right one…
6. Style ps = phoneNumber.getUnselectedStyle();
Style cs = countryCodeButton.getUnselectedStyle();
int pl = cs.getPaddingLeft(isRTL());
int pr = cs.getPaddingRight(isRTL());
countryCodeButton.getAllStyles().setPaddingUnit(Style.UNIT_TYPE_PIXELS);
countryCodeButton.getAllStyles().setPadding(ps.getPaddingTop(),
ps.getPaddingBottom(), pl, pr);
setEditOnShow(phoneNumber);
FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
fab.addActionListener(e -> {
String number = phoneNumber.getText();
if(number.startsWith("0")) {
number = number.substring(1);
}
new EnterSMSVerificationDigitsForm(countryCodeButton.getText() +
"-" + number).show();
});
}
}
EnterMobileNumberForm
You can start editing a text field by invoking startEditing. However, this is a bit more challenging to do with a form that isn't showing yet so we have a special case for
that: setEditOnShow.
And this is pretty much it for this form…
8. public class EnterSMSVerificationDigitsForm extends Form {
public EnterSMSVerificationDigitsForm(String phone) {
super(new BorderLayout());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
Container box = new Container(BoxLayout.y());
box.setScrollableY(true);
box.add(new SpanLabel("Enter the 4-digit code sent to you at " + phone,
"FlagButton"));
TextField[] digits = createDigits(4);
setEditOnShow(digits[0]);
box.add(BoxLayout.encloseX(digits));
SpanLabel error = new SpanLabel("The SMS passcode you've entered is incorrect",
"ErrorLabel");
error.setVisible(false);
box.add(error);
add(CENTER, box);
Label resend = new Label("Resend code in 00:12", "ResendCode");
add(SOUTH, resend);
FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
EnterSMSVerificationDigitsForm
The EnterSMSVerificationDigitsForm is a bit of a mouthful but it describes the function of the form rather well. Lets go over this form line by line…
We use a border layout and place a box in the center which we make scrollable on the Y axis. The reason for the border layout is so we can stick the countdown label in
the south. Otherwise I would have used box layout for the entire form.
Notice I set the container to be scrollable on the Y axis, this is important for containers where we have text input. It allows our keyboard code to resize the container
properly when the keyboard shows.
I’d like to also point out that I used the standard back command in the toolbar.
9. public class EnterSMSVerificationDigitsForm extends Form {
public EnterSMSVerificationDigitsForm(String phone) {
super(new BorderLayout());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
Container box = new Container(BoxLayout.y());
box.setScrollableY(true);
box.add(new SpanLabel("Enter the 4-digit code sent to you at " + phone,
"FlagButton"));
TextField[] digits = createDigits(4);
setEditOnShow(digits[0]);
box.add(BoxLayout.encloseX(digits));
SpanLabel error = new SpanLabel("The SMS passcode you've entered is incorrect",
"ErrorLabel");
error.setVisible(false);
box.add(error);
add(CENTER, box);
Label resend = new Label("Resend code in 00:12", "ResendCode");
add(SOUTH, resend);
FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
EnterSMSVerificationDigitsForm
We create an array of text fields to loop over. This allows us to easily change the code to accept 6 digits. I’ll discuss the createDigits method soon
10. public class EnterSMSVerificationDigitsForm extends Form {
public EnterSMSVerificationDigitsForm(String phone) {
super(new BorderLayout());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
Container box = new Container(BoxLayout.y());
box.setScrollableY(true);
box.add(new SpanLabel("Enter the 4-digit code sent to you at " + phone,
"FlagButton"));
TextField[] digits = createDigits(4);
setEditOnShow(digits[0]);
box.add(BoxLayout.encloseX(digits));
SpanLabel error = new SpanLabel("The SMS passcode you've entered is incorrect",
"ErrorLabel");
error.setVisible(false);
box.add(error);
add(CENTER, box);
Label resend = new Label("Resend code in 00:12", "ResendCode");
add(SOUTH, resend);
FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
EnterSMSVerificationDigitsForm
Yes, this works! It adds all the components in the array so it will add the 4 digit text fields
11. public class EnterSMSVerificationDigitsForm extends Form {
public EnterSMSVerificationDigitsForm(String phone) {
super(new BorderLayout());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
Container box = new Container(BoxLayout.y());
box.setScrollableY(true);
box.add(new SpanLabel("Enter the 4-digit code sent to you at " + phone,
"FlagButton"));
TextField[] digits = createDigits(4);
setEditOnShow(digits[0]);
box.add(BoxLayout.encloseX(digits));
SpanLabel error = new SpanLabel("The SMS passcode you've entered is incorrect",
"ErrorLabel");
error.setVisible(false);
box.add(error);
add(CENTER, box);
Label resend = new Label("Resend code in 00:12", "ResendCode");
add(SOUTH, resend);
FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
EnterSMSVerificationDigitsForm
The error label is always there we just hide it
12. public class EnterSMSVerificationDigitsForm extends Form {
public EnterSMSVerificationDigitsForm(String phone) {
super(new BorderLayout());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
Container box = new Container(BoxLayout.y());
box.setScrollableY(true);
box.add(new SpanLabel("Enter the 4-digit code sent to you at " + phone,
"FlagButton"));
TextField[] digits = createDigits(4);
setEditOnShow(digits[0]);
box.add(BoxLayout.encloseX(digits));
SpanLabel error = new SpanLabel("The SMS passcode you've entered is incorrect",
"ErrorLabel");
error.setVisible(false);
box.add(error);
add(CENTER, box);
Label resend = new Label("Resend code in 00:12", "ResendCode");
add(SOUTH, resend);
FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
EnterSMSVerificationDigitsForm
For now I don't animate the resend text. Again, notice that I use BorderLayout to position the resend label at the bottom and place the rest of the stuff in a BoxLayout in
the center
13. FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
fab.addActionListener(e -> {
if(!isValid(toString(digits))) {
error.setVisible(true);
errorFields(digits);
repaint();
return;
}
new EnterPasswordForm().show();
});
}
private TextField[] createDigits(int count) {
TextField[] response = new TextField[count];
for(int iter = 0 ; iter < count ; iter++) {
TextField t = new TextField("", "0", 1, TextField.NUMERIC);
t.setUIID("Digit");
t.getHintLabel().getAllStyles().setAlignment(CENTER);
response[iter] = t;
}
EnterSMSVerificationDigitsForm
When the floating action button is pressed we validate the input so we can decide whether to show an error or proceed
14. FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
fab.addActionListener(e -> {
if(!isValid(toString(digits))) {
error.setVisible(true);
errorFields(digits);
repaint();
return;
}
new EnterPasswordForm().show();
});
}
private TextField[] createDigits(int count) {
TextField[] response = new TextField[count];
for(int iter = 0 ; iter < count ; iter++) {
TextField t = new TextField("", "0", 1, TextField.NUMERIC);
t.setUIID("Digit");
t.getHintLabel().getAllStyles().setAlignment(CENTER);
response[iter] = t;
}
EnterSMSVerificationDigitsForm
The generic creation code creates the array of numeric text fields and aligns the hints to the center
15. private TextField[] createDigits(int count) {
TextField[] response = new TextField[count];
for(int iter = 0 ; iter < count ; iter++) {
TextField t = new TextField("", "0", 1, TextField.NUMERIC);
t.setUIID("Digit");
t.getHintLabel().getAllStyles().setAlignment(CENTER);
response[iter] = t;
}
for(int iter = 0 ; iter < count - 1 ; iter++) {
onTypeNext(response[iter], response[iter + 1]);
}
return response;
}
private void errorFields(TextField... fields) {
for(TextField f : fields) {
f.getAllStyles().setBorder(Border.createUnderlineBorder(2, 0xcc0000));
f.getSelectedStyle().setBorder(Border.createUnderlineBorder(4, 0xcc0000));
}
}
EnterSMSVerificationDigitsForm
This logic makes sure that once we type a character the input will automatically move to the next text field
16. private TextField[] createDigits(int count) {
TextField[] response = new TextField[count];
for(int iter = 0 ; iter < count ; iter++) {
TextField t = new TextField("", "0", 1, TextField.NUMERIC);
t.setUIID("Digit");
t.getHintLabel().getAllStyles().setAlignment(CENTER);
response[iter] = t;
}
for(int iter = 0 ; iter < count - 1 ; iter++) {
onTypeNext(response[iter], response[iter + 1]);
}
return response;
}
private void errorFields(TextField... fields) {
for(TextField f : fields) {
f.getAllStyles().setBorder(Border.createUnderlineBorder(2, 0xcc0000));
f.getSelectedStyle().setBorder(Border.createUnderlineBorder(4, 0xcc0000));
}
}
EnterSMSVerificationDigitsForm
In case of an error we just change the underline style. We could have also done this by invoking setUIID which might have been more elegant
17. private void errorFields(TextField... fields) {
for(TextField f : fields) {
f.getAllStyles().setBorder(Border.createUnderlineBorder(2, 0xcc0000));
f.getSelectedStyle().setBorder(Border.createUnderlineBorder(4, 0xcc0000));
}
}
private String toString(TextField[] digits) {
StringBuilder s = new StringBuilder();
for(TextField t : digits) {
s.append(t.getAsInt(0));
}
return s.toString();
}
public boolean isValid(String s) {
return s.startsWith("0");
}
private void onTypeNext(TextField current, TextField next) {
current.addDataChangedListener((i, ii) -> {
if(current.getText().length() == 1) {
current.stopEditing();
next.startEditingAsync();
}
});
EnterSMSVerificationDigitsForm
We bind a listener to each text field and if the length of the text is 1 we stop editing and move to the next text field. And this is pretty much it with the exception of the
styles we had to add to make this happen…
30. public class EnterPasswordForm extends Form {
public EnterPasswordForm() {
super(new BorderLayout());
Form previous = getCurrentForm();
getToolbar().setBackCommand("", Toolbar.BackCommandPolicy.AS_ARROW,
e -> previous.showBack());
Container box = new Container(BoxLayout.y());
box.setScrollableY(true);
box.add(new SpanLabel("Welcome back, signin to continue", "FlagButton"));
TextField password = new TextField("", "Enter your password", 80, TextField.PASSWORD);
setEditOnShow(password);
box.add(password);
SpanLabel error = new SpanLabel("Password error", "ErrorLabel");
error.setVisible(false);
box.add(error);
add(CENTER, box);
Button forgot = new Button("I forgot my password", "ForgotPassword");
Button account = new Button("I don't have an account", "ForgotPassword");
add(SOUTH, BoxLayout.encloseY(forgot, account));
FloatingActionButton fab = FloatingActionButton.createFAB(
FontImage.MATERIAL_ARROW_FORWARD);
fab.bindFabToContainer(this);
fab.addActionListener(e -> {
new MapForm().show();
});
}
}
EnterPasswordForm
This is the entire form code literally in one page. After the activation form there is literally nothing new or interesting here.
The only aspect that’s here and wasn’t there is the “ForgotPassword” UIID which we align with the floating action button. In this case we have 2 elements that we enclose
in a box layout Y in the south. Most of the work here is in the UIID itself.