com.waz.zclient.pages.main.profile.preferences.dialogs.AddPhoneNumberPreferenceDialogFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.waz.zclient.pages.main.profile.preferences.dialogs.AddPhoneNumberPreferenceDialogFragment.java

Source

/**
 * Wire
 * Copyright (C) 2016 Wire Swiss GmbH
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.waz.zclient.pages.main.profile.preferences.dialogs;

import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.graphics.drawable.DrawableWrapper;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.AppCompatDrawableManager;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.waz.api.CredentialsUpdateListener;
import com.waz.zclient.R;
import com.waz.zclient.core.stores.appentry.AppEntryError;
import com.waz.zclient.newreg.fragments.country.Country;
import com.waz.zclient.newreg.fragments.country.CountryController;
import com.waz.zclient.pages.BaseDialogFragment;
import com.waz.zclient.ui.utils.DrawableUtils;
import com.waz.zclient.ui.utils.MathUtils;
import com.waz.zclient.utils.PermissionUtils;
import com.waz.zclient.utils.ViewUtils;

public class AddPhoneNumberPreferenceDialogFragment extends
        BaseDialogFragment<AddPhoneNumberPreferenceDialogFragment.Container> implements CountryController.Observer {

    public static final String TAG = AddPhoneNumberPreferenceDialogFragment.class.getSimpleName();
    private static final String ARG_PHONE = "ARG_PHONE";

    private static final String[] GET_PHONE_NUMBER_PERMISSIONS = new String[] {
            Manifest.permission.READ_PHONE_STATE };

    // values from TextInputLayout to act the same
    private static final long ANIMATION_DURATION = 200L;
    private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR = new FastOutLinearInInterpolator();
    private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR = new LinearOutSlowInInterpolator();

    private boolean isEditMode;
    private View containerView;
    private EditText phoneEditText;
    private EditText countryEditText;
    private TextView errorView;
    private CountryController countryController;

    public static Fragment newInstance() {
        return newInstance(null);
    }

    public static Fragment newInstance(String phoneNumber) {
        final AddPhoneNumberPreferenceDialogFragment fragment = new AddPhoneNumberPreferenceDialogFragment();
        final Bundle args = new Bundle();
        args.putString(ARG_PHONE, phoneNumber);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        isEditMode = !TextUtils.isEmpty(getArguments().getString(ARG_PHONE));
    }

    @SuppressLint("InflateParams")
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final LayoutInflater inflater = LayoutInflater.from(getContext());
        final View view = inflater.inflate(R.layout.preference_dialog_add_phone, null);

        containerView = ViewUtils.getView(view, R.id.ll__preferences__container);
        errorView = ViewUtils.getView(view, R.id.tv__preferences__error);
        errorView.setVisibility(View.GONE);
        countryController = new CountryController(getActivity());

        countryEditText = ViewUtils.getView(view, R.id.acet__preferences__country);
        phoneEditText = ViewUtils.getView(view, R.id.acet__preferences__phone);
        phoneEditText.requestFocus();
        phoneEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_DONE) {
                    handleInput();
                    return true;
                } else {
                    return false;
                }
            }
        });

        final String phoneNumber = getArguments().getString(ARG_PHONE, "");
        final String number = countryController.getPhoneNumberWithoutCountryCode(phoneNumber);
        final String countryCode = phoneNumber.substring(0, phoneNumber.length() - number.length()).replace("+",
                "");
        phoneEditText.setText(number);
        phoneEditText.setSelection(number.length());
        countryEditText.setText(String.format("+%s", countryCode));

        if (isEditMode) {
            phoneEditText.requestFocus();
        } else {
            countryEditText.requestFocus();
        }

        final AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
                .setTitle(isEditMode ? R.string.pref__account_action__dialog__edit_phone__title
                        : R.string.pref__account_action__dialog__add_phone__title)
                .setView(view).setPositiveButton(android.R.string.ok, null)
                .setNegativeButton(android.R.string.cancel, null).create();
        alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
        return alertDialog;
    }

    @Override
    public void onStart() {
        super.onStart();
        if (!isEditMode && PermissionUtils.hasSelfPermissions(getActivity(), GET_PHONE_NUMBER_PERMISSIONS)) {
            setSimPhoneNumber();
        }
        final AlertDialog dialog = (AlertDialog) getDialog();
        if (dialog == null) {
            return;
        }
        final Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
        positiveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (containerView == null) {
                    dismiss();
                    return;
                }
                handleInput();
            }
        });
        countryController.addObserver(this);
    }

    private void setSimPhoneNumber() {
        if (containerView == null) {
            return;
        }
        final String abbreviation = getControllerFactory().getDeviceUserController().getPhoneCountryISO();
        final String countryCode = new CountryController(getActivity()).getCodeForAbbreviation(abbreviation);
        if (countryCode == null) {
            return;
        }
        final String rawPhoneNumber = getControllerFactory().getDeviceUserController().getPhoneNumber(countryCode);
        phoneEditText.setText(rawPhoneNumber);
        phoneEditText.setSelection(rawPhoneNumber.length());
        countryEditText.setText(String.format("+%s", countryCode.replace("+", "")));
    }

    @Override
    public void onStop() {
        countryController.removeObserver(this);
        super.onStop();
    }

    @Override
    public void onDestroyView() {
        containerView = null;
        countryEditText = null;
        phoneEditText = null;
        errorView = null;
        super.onDestroyView();
    }

    private void handleInput() {
        if (containerView == null) {
            dismiss();
            return;
        }
        final String countryCode = countryEditText.getText().toString().trim();
        if (TextUtils.isEmpty(countryCode) || !countryCode.matches("\\+([0-9])+")) {
            showError(getString(R.string.pref__account_action__dialog__add_phone__error__country));
            return;
        }
        final String rawNumber = phoneEditText.getText().toString().trim();
        if (TextUtils.isEmpty(rawNumber)) {
            showError(getString(R.string.pref__account_action__dialog__add_phone__error__number));
            return;
        }
        final String number = String.format("%s%s", countryCode, rawNumber);
        if (number.equalsIgnoreCase(getStoreFactory().getProfileStore().getMyPhoneNumber())) {
            dismiss();
            return;
        }
        showError(null);
        ViewUtils.showAlertDialog(getActivity(),
                getString(R.string.pref__account_action__dialog__add_phone__confirm__title),
                getString(R.string.pref__account_action__dialog__add_phone__confirm__message, number),
                getString(android.R.string.ok), getString(android.R.string.cancel),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (getStoreFactory() == null) {
                            return;
                        }
                        getStoreFactory().getProfileStore().setMyPhoneNumber(number,
                                new CredentialsUpdateListener() {
                                    @Override
                                    public void onUpdated() {
                                        if (getContainer() == null) {
                                            return;
                                        }
                                        getContainer().onVerifyPhone(number);
                                    }

                                    @Override
                                    public void onUpdateFailed(int errorCode, String message, String label) {
                                        if (containerView == null) {
                                            return;
                                        }
                                        if (AppEntryError.PHONE_EXISTS.correspondsTo(errorCode, label)) {
                                            showError(getString(AppEntryError.PHONE_EXISTS.headerResource));
                                        } else {
                                            showError(getString(
                                                    AppEntryError.PHONE_REGISTER_GENERIC_ERROR.headerResource));
                                        }
                                    }
                                });
                    }
                }, null);
    }

    // from TextInputLayout
    private void showError(final String error) {
        if (TextUtils.equals(errorView.getText(), error)) {
            return;
        }

        final boolean animate = ViewCompat.isLaidOut(containerView);
        final boolean errorShown = !TextUtils.isEmpty(error);

        ViewCompat.animate(errorView).cancel();
        if (errorShown) {
            errorView.setText(error);
            errorView.setVisibility(View.VISIBLE);

            if (animate) {
                if (MathUtils.floatEqual(ViewCompat.getAlpha(errorView), 1f)) {
                    ViewCompat.setAlpha(errorView, 0f);
                }
                ViewCompat.animate(errorView).alpha(1f).setDuration(ANIMATION_DURATION)
                        .setInterpolator(LINEAR_OUT_SLOW_IN_INTERPOLATOR)
                        .setListener(new ViewPropertyAnimatorListenerAdapter() {
                            @Override
                            public void onAnimationStart(View view) {
                                view.setVisibility(View.VISIBLE);
                            }
                        }).start();
            }
        } else {
            if (errorView.getVisibility() == View.VISIBLE) {
                if (animate) {
                    ViewCompat.animate(errorView).alpha(0f).setDuration(ANIMATION_DURATION)
                            .setInterpolator(FAST_OUT_LINEAR_IN_INTERPOLATOR)
                            .setListener(new ViewPropertyAnimatorListenerAdapter() {
                                @Override
                                public void onAnimationEnd(View view) {
                                    errorView.setText(error);
                                    view.setVisibility(View.INVISIBLE);

                                    updateEditTextBackground(countryEditText);
                                    updateEditTextBackground(phoneEditText);
                                }
                            }).start();
                } else {
                    errorView.setVisibility(View.INVISIBLE);
                }
            }
        }

        updateEditTextBackground(countryEditText);
        updateEditTextBackground(phoneEditText);
    }

    // from TextInputLayout
    private void updateEditTextBackground(EditText editText) {
        ensureBackgroundDrawableStateWorkaround(editText);

        Drawable editTextBackground = editText.getBackground();
        if (editTextBackground == null) {
            return;
        }

        if (android.support.v7.widget.DrawableUtils.canSafelyMutateDrawable(editTextBackground)) {
            editTextBackground = editTextBackground.mutate();
        }

        if (errorView != null && errorView.getVisibility() == View.VISIBLE) {
            // Set a color filter of the error color
            editTextBackground.setColorFilter(AppCompatDrawableManager
                    .getPorterDuffColorFilter(errorView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN));
        } else {
            // Else reset the color filter and refresh the drawable state so that the
            // normal tint is used
            clearColorFilter(editTextBackground);
            editText.refreshDrawableState();
        }
    }

    // from TextInputLayout
    @TargetApi(Build.VERSION_CODES.KITKAT)
    private static void clearColorFilter(@NonNull Drawable drawable) {
        drawable.clearColorFilter();

        if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
            // API 21 + 22 have an issue where clearing a color filter on a DrawableContainer
            // will not propagate to all of its children. To workaround this we unwrap the drawable
            // to find any DrawableContainers, and then unwrap those to clear the filter on its
            // children manually
            if (drawable instanceof InsetDrawable) {
                clearColorFilter(((InsetDrawable) drawable).getDrawable());
            } else if (drawable instanceof DrawableWrapper) {
                clearColorFilter(((DrawableWrapper) drawable).getWrappedDrawable());
            } else if (drawable instanceof DrawableContainer) {
                final DrawableContainer container = (DrawableContainer) drawable;
                final DrawableContainer.DrawableContainerState state = (DrawableContainer.DrawableContainerState) container
                        .getConstantState();
                if (state != null) {
                    for (int i = 0, count = state.getChildCount(); i < count; i++) {
                        clearColorFilter(state.getChild(i));
                    }
                }
            }
        }
    }

    // from TextInputLayout
    private void ensureBackgroundDrawableStateWorkaround(EditText editText) {
        final int sdk = Build.VERSION.SDK_INT;
        if (sdk != 21 && sdk != 22) {
            // The workaround is only required on API 21-22
            return;
        }
        final Drawable bg = editText.getBackground();
        if (bg == null) {
            return;
        }

        // There is an issue in the platform which affects container Drawables
        // where the first drawable retrieved from resources will propogate any changes
        // (like color filter) to all instances from the cache. We'll try to workaround it...

        final Drawable newBg = bg.getConstantState().newDrawable();

        boolean hasReconstructedEditTextBackground = false;
        if (bg instanceof DrawableContainer) {
            // If we have a Drawable container, we can try and set it's constant state via
            // reflection from the new Drawable
            hasReconstructedEditTextBackground = DrawableUtils.setContainerConstantState((DrawableContainer) bg,
                    newBg.getConstantState());
        }

        if (!hasReconstructedEditTextBackground) {
            // If we reach here then we just need to set a brand new instance of the Drawable
            // as the background. This has the unfortunate side-effect of wiping out any
            // user set padding, but I'd hope that use of custom padding on an EditText
            // is limited.
            editText.setBackgroundDrawable(newBg);
        }
    }

    @Override
    public void onCountryHasChanged(Country country) {
        final String phoneNumber = getArguments().getString(ARG_PHONE, "");
        final String number = countryController.getPhoneNumberWithoutCountryCode(phoneNumber);
        final String countryCode = phoneNumber.substring(0, phoneNumber.length() - number.length()).replace("+",
                "");
        if (!TextUtils.isEmpty(countryCode)) {
            return;
        }

        countryEditText.setText(String.format("+%s", country.getCountryCode()));
    }

    public interface Container {
        void onVerifyPhone(String phoneNumber);
    }
}