UI Components

UIComponents library provides components such as ListMenuFragment, InfoDialog, InputListFragment and NumPadDialog which will be explained below.

Building The Project

In order to use UI Components, you have to migrate your Android project to AndroidX. In Android Studio, on top menu, click on Refactor and then Migrate to AndroidX, proceed to further steps.

Download the zip file below and extract it under your project folder; MyApplication\app\libs

Changelog

DetailDate

Added color and numpad screen for 330TR devices.

02.05.2024

Validation bug fixed when default value set on customInputFormat.

23.07.2024

Multi-conditional and multi-message added for customInputFormat.

Validate control updated fragment on first call

05.08.2024

Turkish character support added to QR.

02.09.2024

Bug fix for 1000TR menu

09.09.2024

QrScreen330 updated.

12.09.2024

Lib Update: 12.09.2024

Place the uicomponents-release.aar library file into your project's "libs" folder and add it as dependency inside your app level build.gradle file like below and build your project in order to create build classes of library.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
    ...
}

Minimum SDK version must be 25 or higher in your app level build.gradle file.

defaultConfig {
        minSdkVersion 25
...
    }

App Theme

For creating all the apps identical, you have to set the colors and themes. Under your application res folder, values -> colors.xml copy and paste these resources.

<resources>
    <color name="colorPrimary">#5ea9db</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#5ea9db</color>
    <color name="translucent_black">#90000000</color>
    <color name="white">#FFFFFF</color>
    <color name="green">#00FF00</color>
    <color name="dark_gray">#212121</color>
    <color name="gray_black">#191919</color>
    <color name="black_overlay">#66000000</color>
    <color name="black">#000000</color>
    <color name="transparent_black">#a0000000</color>
    <color name="color_token">#45ABE0</color>
    <color name="blue_theme">#114CEE</color>
</resources>

Under your res -> values -> themes.xml file or res -> values -> themes -> themes.xml file copy and paste the theme resources.

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/dark_gray</item>
        <item name="colorPrimaryDark">@color/dark_gray</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="checkboxStyle">@style/AppTheme.DefaultCheckBoxStyle</item>
        <item name="android:textViewStyle">@style/AppTheme.DefaultTextStyle</item>
        <item name="buttonStyle">@style/AppTheme.DefaultButtonStyle</item>
        <item name="android:windowBackground">@color/black</item>
        <item name="android:windowAnimationStyle">@null</item>
    </style>

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.DefaultTextStyle" parent="android:Widget.TextView">
        <item name="android:fontFamily">@font/montserrat_regular</item>
    </style>
    <style name="AppTheme.DefaultCheckBoxStyle" parent="android:Widget.CompoundButton.CheckBox">
        <item name="android:fontFamily">@font/montserrat_regular</item>
    </style>
    <style name="AppTheme.DefaultButtonStyle" parent="android:Widget.Button">
        <item name="android:fontFamily">@font/montserrat_semibold</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />

    <style name="EditTextTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorControlNormal">@android:color/white</item>
        <item name="colorControlActivated">@color/colorPrimary</item>
        <item name="colorControlHighlight">@color/colorPrimary</item>
    </style>

    <style name="HeaderBar">
        <item name="android:background">#303F9F</item>
    </style>

    <style name="FullscreenTheme" parent="AppTheme">
        <item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
        <item name="android:windowActionBarOverlay">true</item>
        <item name="android:windowBackground">@null</item>
        <item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
        <item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
    </style>

    <style name="FullscreenActionBarStyle" parent="Widget.AppCompat.ActionBar">
        <item name="android:background">@color/black_overlay</item>
    </style>
</resources>

If your device is 330TR, you can use blue_theme in colors for update the AppTheme's color. By default, it is dark gray.

Right click to your values folder and click to new value resource file, name it as attrs.xml

res -> values -> attrs.xml file

<resources>
    <declare-styleable name="ButtonBarContainerTheme">
        <attr name="metaButtonBarStyle" format="reference" />
        <attr name="metaButtonBarButtonStyle" format="reference" />
    </declare-styleable>
</resources>

Finally you have to change the theme name under your AndroidManifest.xml file.

<application
       ...
       android:theme="@style/AppTheme">  <!-- With Actionbar -->
       <!-- OR --> 
       android:theme="@style/AppTheme.NoActionBar"> <!-- Token theme without Actionbar -->
        ...
</application>

After setting your theme, your activities have to look like this(empty activity);

ListMenuFragment

A fragment component which allows you to show menu items, listen menu item click events and create an automate menu tree process.

First, create your menu item class by implementing IListMenuItem interface or implement this interface from your existing data class which will be listed as menu items. Override implemented methods; getName(): Return the string that will be dislayed on menu item. getSubMenuItemList(): If a sub menu will be shown on menu item click, return list of your menu items, otherwise return null. @Nullable MenuItemClickListener getClickListener(): Each menu item's click listener seperately. @Nullable IAuthenticator getAuthenticator(): If a menu item's click event must be authorized, return an IAuthenticator object, then a pinpad screen will be shown when this menu item clicked. Authorize the password and menu item's click event will be executed. If authorization not necessary, return null for this method.

Note: Your menu items' click events will be called regardless of a sub menu will be shown or not.

Example:

public class MenuItem implements IListMenuItem {
    private String mTitle;
    private List<IListMenuItem> subMenuItemList;
    private MenuItemClickListener mListener;
    private IAuthenticator mAuthenticator;
    private Object arg;

    public MenuItem(String title, MenuItemClickListener listener) {
        this(title, listener, null, null);
    }

    public MenuItem(String title, MenuItemClickListener listener, @Nullable IAuthenticator authenticator) {
        this(title, listener, null, authenticator);
    }

    public MenuItem(String title, List<IListMenuItem> subMenuItemList, @Nullable IAuthenticator authenticator) {
        this(title, null, subMenuItemList, authenticator);
    }

    public MenuItem(String title, @Nullable MenuItemClickListener listener, @Nullable List<IListMenuItem> subMenuItemList, @Nullable IAuthenticator authenticator) {
        this.mTitle = title;
        this.mListener = listener;
        this.subMenuItemList = subMenuItemList;
        this.mAuthenticator = authenticator;
    }

    @Override
    public String getName() {
        return mTitle;
    }

    public void setArg(Object arg) {
        this.arg = arg;
    }

    public Object getArg() {
        return arg;
    }

    @Nullable
    @Override
    public List<IListMenuItem> getSubMenuItemList() {
        return subMenuItemList;
    }

    @Nullable
    @Override
    public MenuItemClickListener getClickListener() {
        return mListener;
    }

    @Nullable
    @Override
    public IAuthenticator getAuthenticator() {
        return mAuthenticator;
    }
}

You can create a class named BaseActivity and use the addFragment, replaceFragment, removeFragment and also, you can use InfoDialog's with calling from the BaseActivity.

public abstract class BaseActivity extends TimeOutActivity {
    /**
     * Returns time out value in seconds for activities which extend
     * @see TimeOutActivity
     * In case of any user interaction, timeout timer will be reset.
     *
     * If any activity will not have time out,
     * override this method from that activity and @return '0'.
     */
    @Override
    protected int getTimeOutSec() {
        return 60;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    protected void addFragment(@IdRes Integer resourceId, Fragment fragment, Boolean addToBackStack) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(resourceId, fragment);
        if (addToBackStack) {
            ft.addToBackStack("");
        }
        ft.commit();
    }

    protected void replaceFragment(@IdRes Integer resourceId, Fragment fragment, Boolean addToBackStack) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(resourceId, fragment);
        if (addToBackStack) {
            ft.addToBackStack("");
        }
        ft.commit();
    }

    protected void removeFragment(Fragment fragment) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.remove(fragment);
        ft.commit();
    }
}

In your MainActivity (or another Activity class) you can list the menu items like this;

public class MainActivity extends BaseActivity implements InfoDialogListener {

    private List<IListMenuItem> menuItems = new ArrayList<>();
    private WeakReference<Context> mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        prepareData(); // Call the menu list item preparing method
        ListMenuFragment fragment = ListMenuFragment.newInstance(menuItems, "", false, null);
        addFragment(R.id.container, fragment, false);
    }

    public void prepareData() {

        menuItems.add(new MenuItem("Menu Item 1", (menuItem) -> {
            Toast.makeText(this, "Menu Item 1", Toast.LENGTH_LONG).show();
        }));

        menuItems.add(new MenuItem("Menu Item 2", (menuItem) -> {
            Toast.makeText(this, "Menu Item 1", Toast.LENGTH_LONG).show();
        }));

        menuItems.add(new MenuItem("Menu Item 3", new MenuItemClickListener<MenuItem>() {
            @Override
            public void onClick(MenuItem menuItem) {
                Toast.makeText(MainActivity.this, "Menu Item 3", Toast.LENGTH_LONG).show();
            }
        }));   
    }
}

Before runing your app you have to set the activity layout file, in your activity_main.xml or another layout file which you are using.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/activity_main"
    android:background="@color/dark_gray"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

ListMenuFragment - SubList

With help of clicking to a menu item you can update the fragment and create a sub menu with sub menu items. Under the prepareData() which we created previously, you have to add another list of sub menu items.

 public void prepareData() {

        List<IListMenuItem> subList1 = new ArrayList<>(); // Creating a list for your sub menu items
        /* Your Sub List Items*/
        subList1.add(new MenuItem("MenuItem 1", (menuItem) -> {

            Toast.makeText(this,"Sub Menu 1", Toast.LENGTH_LONG).show();

        }, null));

        subList1.add(new MenuItem("MenuItem 2", (menuItem) -> {

            Toast.makeText(this,"Sub Menu 2", Toast.LENGTH_LONG).show();

        }, null));
        subList1.add(new MenuItem("MenuItem 3", (menuItem) -> {

            Toast.makeText(this,"Sub Menu 3", Toast.LENGTH_LONG).show();

        }, null));

        menuItems.add(new MenuItem("Sub Menu", subList1, null)); // The menu item in to the List Menu Fragment
}

ConfirmationDialog

At the previous example; we created a BaseActivity and implemented it from MainActivity, you can add your Confirmation Dialog methods to there.

    /**
     * Shows a dialog to the user which asks for a confirmation.
     * Dialog will be dismissed automatically when user taps on to confirm/cancel button.
     * See {@link InfoDialog#newInstance(InfoDialog.InfoType, String, String, InfoDialog.InfoDialogButtons, int, InfoDialogListener)}
     */
    protected InfoDialog showConfirmationDialog(InfoDialog.InfoType type, String title, String info, InfoDialog.InfoDialogButtons buttons, int arg, InfoDialogListener listener) {
        InfoDialog dialog = InfoDialog.newInstance(type, title, info, buttons, arg, listener);
        dialog.show(getSupportFragmentManager(), "");
        return dialog;
    }

Confirmation Dialog can have various information types. The base code to call a Confirmation Dialog is;

showConfirmationDialog(InfoDialog.InfoType.Warning,"Warning", "Confirmation: Warning", InfoDialog.InfoDialogButtons.Both, 99, MainActivity.this)));

For this example we have a warning info type. After the InfoType. you can add Uploading, QR, Progress, Processing, None, Info, Error, Downloading, Declined, Connecting, Confirmed. Also, you can cahnge the confirmation type.

After the InfoDialog.InfoDialogButtons. you can add Both, Confirm, Cancel for restricting the buttons.

Implementing the confirm and cancel actions.

    @Override
    public void confirmed(int arg) {
        if (arg == 99) {
              //  Do something else... 
        }
        //else if (arg == ***) { Do something else... }
    }

    @Override
    public void canceled(int arg) {
        if (arg == 99) {
             //  Do something else... 
        }
        //else if (arg == ***) { Do something else... }
    }

InfoDialog

InfoDialog component to show the user information about the process.

InfoDialog's have same Dialog types Uploading, QR, Progress, Processing, None, Info, Error, Downloading, Declined, Connecting, Confirmed and has a isCancelable selection true or false.

Usage is like below(you can place the code in the BaseActivity.java):

    /**
     * Shows a dialog to the user which informs the user about current progress.
     * See {@link InfoDialog#newInstance(InfoDialog.InfoType, String, boolean)}
     * Dialog can dismissed by calling .dismiss() method of the fragment instance returned from this method.
     */
    protected InfoDialog showInfoDialog(InfoDialog.InfoType type, String text, boolean isCancelable) {
        InfoDialog fragment = InfoDialog.newInstance(type, text, isCancelable);
        fragment.show(getSupportFragmentManager(), "");
        return fragment;
    }

    /**
     * Shows a dialog to the user which asks for a confirmation.
     * Dialog will be dismissed automatically when user taps on to confirm/cancel button.
     * See {@link InfoDialog#newInstance(InfoDialog.InfoType, String, String, InfoDialog.InfoDialogButtons, int, InfoDialogListener)}
     */
    protected InfoDialog showConfirmationDialog(InfoDialog.InfoType type, String title, String info, InfoDialog.InfoDialogButtons buttons, int arg, InfoDialogListener listener) {
        InfoDialog dialog = InfoDialog.newInstance(type, title, info, buttons, arg, listener);
        dialog.show(getSupportFragmentManager(), "");
        return dialog;
    }

Calling the InfoDialog;

showInfoDialog(InfoDialog.InfoType.Confirmed, "Confirmed", true);

Updating The InfoDialog; In the InfoDialog library there is an update method for changing the existing InfoDialog.

Example usage of the update dialog;

private void printSlip() {
        if (response == true) {
            print(CustomerSlip);
            InfoDialog printDialog = UIDialog.showConfirmationDialog((AppCompatActivity) mContext.get(), InfoDialog.InfoType.Info, "Merchant slip:", "Merchant slip will be printed", InfoDialog.InfoDialogButtons.Confirm, 0, new InfoDialogListener() {
                @Override
                public void confirmed(int i) {
                    isConfirmedMerchantSlip = true;
                }
                @Override
                public void canceled(int i) {}
            });
            while (!isConfirmedMerchantSlip) {
                sleep(); // Sleep for 500ms
                TimeCounter++; // Count the sleep
                if(voidTimeCounter > 20){ isConfirmedMerchantSlip = true; } // If merchant waits for 10 seconds print the merchant slip
            }
            printDialog.update(InfoDialog.InfoType.Progress,"Printing The Merchant Slip");
            print(MerchantSlip);
            printDialog.update(InfoDialog.InfoType.Confirmed, "Merchant Slip Printed");
        }
        else {
            print(FailSlip);
        }
    }

QR Code

Showing a QR Code on the InfoDialog; In the InfoDialog library there is a setQr method for showing a QR code on the existing InfoDialog.

For a QR Code payment first of all you have to use the google.zxing library. Implementation of the library;

    implementation 'com.google.zxing:core:3.2.0'
    implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar'

Add the QR Payment object to the ReadCard Method, detailed explanation; Card Service

obj.put("qrPay", 1);

Sample QR Request Method

protected void QrSale() {
        InfoDialog dialog = showInfoDialog(InfoDialog.InfoType.Progress, "Please Wait", true);
        // Request to Show a QR Code ->
        cardServiceBinding.showQR("PLEASE READ THE QR CODE", StringHelper.getAmount(amount), qrString); // Shows QR on the back screen
        dialog.setQr(qrString, "Waiting For The QR Code To Read"); // Shows the same QR on Info Dialog
        // Request a QR Response ->

            /*
            * Your QR Response
            */

        dialog.setDismissedListener(() -> { // This method listens for the dismiss of the QR Dialog
            // You can call your QR Payment Cancel method here
        });
    } 

InputListFragment

You can create an input list by creating a CustomInputFormat list and setting it to an InputListFragment.

    /**
     *
     * @param hint: Title to be shown above edittext.
     * @param type: Input format for edittext.
     * @param maxLength: Maximum length for input string. If null, maximum length will not be set.
     * @param invalidMessage: Warning message to be shown under edit text in case input is not valid.
     * @param validator: An object that is a type of InputValidator. Required if related input must be validated.
     *                 If validator is null, input will always be valid no matter if it's empty or not.
     */
    public CustomInputFormat(String hint, EditTextInputType type, @Nullable Integer maxLength,
                             @Nullable String invalidMessage, @Nullable InputValidator validator)

Create your CustomInputFormat list like below:

        List<CustomInputFormat> inputList = new ArrayList<>();

        inputList.add(new CustomInputFormat("Card Number", EditTextInputType.CreditCardNumber, null, "Invalid card number!", validator));
        inputList.add(new CustomInputFormat("Expire Date", EditTextInputType.ExpiryDate, null, null, null));
        inputList.add(new CustomInputFormat("CVV", EditTextInputType.CVV, null, null, null));
        inputList.add(new CustomInputFormat("Amount", EditTextInputType.AmountDot, null, null, null)); // For 1000TR Keyboard, amount input will be 1,00 for 1TL. text.replace(",", "") for 100.

You can set default text to inputs by setting text to CustomInputFormat

CustomInputFormat input = new CustomInputFormat("IP", EditTextInputType.IpAddress, null, null, null);
input.setText("123.456.789.1");

If you want special validator for multi conditional input you can use example code block. This code block allows instant multi-conditional and message serving for the validator:

            InputValidator tempValidator = new InputValidator() {
                @Override
                public boolean validate(CustomInputFormat customInputFormat) {
                    if(customInputFormat.getText().length() == 2){
                        customInputFormat.setInvalidMessage("INPUT LEN SHOULD NOT BE 2");
                        return customInputFormat.getText().length() == 6;
                    }else if(customInputFormat.getText().length() == 3){
                        customInputFormat.setInvalidMessage("INPUT LEN SHOULD NOT BE 3");
                        return customInputFormat.getText().length() == 6;
                    }else{
                        customInputFormat.setInvalidMessage(getString(R.string.auth_code_invalid_six_digit));
                        return customInputFormat.getText().length() == 6;
                    }
                }
            };
            inputAuthCode = new CustomInputFormat(getString(R.string.return_auth_code), EditTextInputType.Text, 6, getString(R.string.auth_code_invalid_six_digit),
                    tempValidator);


            inputList.add(inputAuthCode);

Pass your CustomInputFormat list to InputListFragment constructor method and add your fragment:

        InputListFragment fragment = InputListFragment.newInstance(inputList);
        addFragment(R.id.container, fragment, true);

        //addFragment method should be like:
        protected void addFragment(@IdRes Integer resourceId, Fragment fragment, Boolean addToBackStack) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(resourceId, fragment);
            if (addToBackStack) {
                ft.addToBackStack("");
            }
            ft.commit();
        }

You can check if all fields are valid or not by calling isValid() method of fragment:

    Boolean inputsValid =  fragment.isValid();

If any of the fields has an invalid input, isValid() method will return "false", if all field inputs are valid, it will return "true".

After validating the inputs, you can get the input list by calling getInputList() method which will return list of input strings in order:

    List<String> inputList = fragment.getInputList();

If you activate the Input list fragment on another fragment or activity and want to return to the previous activity, you have to set the addToBackStack false;

addFragment(R.id.container, fragment, false);

NumPadDialog

Numeric PIN input dialog which is shown automatically if a ListMenu item has authenticator, or you can use it independently like below:

NumPadDialog dialog = NumPadDialog.newInstance(new NumPadListener() {
            @Override
            public void enter(String pin) {
                    //Pin entry callback
            }

            @Override
            public void onCanceled() {
                    //Numpad canceled callback
            }
        }, "Please enter PIN", 8); /* Maximum pin length:8, change the integer 8 for the length you want */
        dialog.show(getSupportFragmentManager(), "numpad");

Adding Visual Details to Your App

Your app theme has a harmony but also UIComponents Library gives you permission to customize your app in a limit. You can add your logo and titles to the fragments of your app, you can use backButton's or Android navigation buttons.

Add your my_logo.png file under the res -> drawable folder and call as R.drawable.my_logo.png

Under your Activity.java file, inside the onCreate method you have to create a fragment.

ListMenuFragment fragment = ListMenuFragment.newInstance(menuItems, "POS Operations", false, R.drawable.token_logo); //false for no back button
        addFragment(R.id.container, fragment, false); 
InputListFragment fragment = InputListFragment.newInstance(inputList);
        addFragment(R.id.container, fragment, false); // If you want to go back to the previous activity; set addToBackStack false
        fragment.setActionLayout("Input List", true, R.drawable.token_logo); // true for hasBackButton

Info Dialog - Dialog Types

Dialog types: Confirmed, Warning, Error, Info, Declined, Connecting, Downloading, Uploading, Processing, Progress, None

Confirmation Dialog - Dialog Types

Dialog types: Confirmed, Warning, Error, Info, Declined, Connecting, Downloading, Uploading, Processing, Progress, None

Last updated