/* * Copyright (C) 2015 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Modifications copyright (C) 2017 Google Inc */ package com.firebase.ui.auth.ui.phone; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ListView; import com.firebase.ui.auth.data.model.CountryInfo; import com.firebase.ui.auth.util.ExtraConstants; import com.firebase.ui.auth.util.data.PhoneNumberUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import androidx.annotation.NonNull; import androidx.appcompat.widget.AppCompatEditText; public final class CountryListSpinner extends AppCompatEditText implements View.OnClickListener { private static final String KEY_SUPER_STATE = "KEY_SUPER_STATE"; private static final String KEY_COUNTRY_INFO = "KEY_COUNTRY_INFO"; private final String mTextFormat; private final DialogPopup mDialogPopup; private final CountryListAdapter mCountryListAdapter; private View.OnClickListener mListener; private String mSelectedCountryName; private CountryInfo mSelectedCountryInfo; private Set<String> mWhitelistedCountryIsos = new HashSet<>(); private Set<String> mBlacklistedCountryIsos = new HashSet<>(); public CountryListSpinner(Context context) { this(context, null, android.R.attr.spinnerStyle); } public CountryListSpinner(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.spinnerStyle); } public CountryListSpinner(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); super.setOnClickListener(this); mCountryListAdapter = new CountryListAdapter(getContext()); mDialogPopup = new DialogPopup(mCountryListAdapter); mTextFormat = "%1$s +%2$d"; mSelectedCountryName = ""; } public void init(Bundle params) { if (params != null) { List<CountryInfo> countries = getCountriesToDisplayInSpinner(params); setCountriesToDisplay(countries); setDefaultCountryForSpinner(countries); } } private List<CountryInfo> getCountriesToDisplayInSpinner(Bundle params) { initCountrySpinnerIsosFromParams(params); Map<String, Integer> countryInfoMap = PhoneNumberUtils.getImmutableCountryIsoMap(); // We consider all countries to be whitelisted if there are no whitelisted // or blacklisted countries given as input. if (mWhitelistedCountryIsos.isEmpty() && mBlacklistedCountryIsos.isEmpty()) { this.mWhitelistedCountryIsos = new HashSet<>(countryInfoMap.keySet()); } List<CountryInfo> countryInfoList = new ArrayList<>(); // At this point either mWhitelistedCountryIsos or mBlacklistedCountryIsos is null. // We assume no countries are to be excluded. Here, we correct this assumption based on the // contents of either lists. Set<String> excludedCountries = new HashSet<>(); if (!mBlacklistedCountryIsos.isEmpty()) { // Exclude all countries in the mBlacklistedCountryIsos list. excludedCountries.addAll(mBlacklistedCountryIsos); } else { // Exclude all countries that are not present in the mWhitelistedCountryIsos list. excludedCountries.addAll(countryInfoMap.keySet()); excludedCountries.removeAll(mWhitelistedCountryIsos); } // Once we know which countries need to be excluded, we loop through the country isos, // skipping those that have been excluded. for (String countryIso : countryInfoMap.keySet()) { if (!excludedCountries.contains(countryIso)) { countryInfoList.add(new CountryInfo(new Locale("", countryIso), countryInfoMap.get(countryIso))); } } Collections.sort(countryInfoList); return countryInfoList; } private void initCountrySpinnerIsosFromParams(@NonNull Bundle params) { List<String> whitelistedCountries = params.getStringArrayList(ExtraConstants.WHITELISTED_COUNTRIES); List<String> blacklistedCountries = params.getStringArrayList(ExtraConstants.BLACKLISTED_COUNTRIES); if (whitelistedCountries != null) { mWhitelistedCountryIsos = convertCodesToIsos(whitelistedCountries); } if (blacklistedCountries != null) { mBlacklistedCountryIsos = convertCodesToIsos(blacklistedCountries); } } private Set<String> convertCodesToIsos(@NonNull List<String> codes) { Set<String> isos = new HashSet<>(); for (String code : codes) { if (PhoneNumberUtils.isValid(code)) { isos.addAll(PhoneNumberUtils.getCountryIsosFromCountryCode(code)); } else { isos.add(code); } } return isos; } public void setCountriesToDisplay(List<CountryInfo> countries) { mCountryListAdapter.setData(countries); } private void setDefaultCountryForSpinner(List<CountryInfo> countries) { CountryInfo countryInfo = PhoneNumberUtils.getCurrentCountryInfo(getContext()); if (isValidIso(countryInfo.getLocale().getCountry())) { setSelectedForCountry(countryInfo.getCountryCode(), countryInfo.getLocale()); } else if (countries.iterator().hasNext()) { countryInfo = countries.iterator().next(); setSelectedForCountry(countryInfo.getCountryCode(), countryInfo.getLocale()); } } public boolean isValidIso(String iso) { iso = iso.toUpperCase(Locale.getDefault()); boolean valid = true; if (!mWhitelistedCountryIsos.isEmpty()) { valid = valid && mWhitelistedCountryIsos.contains(iso); } if (!mBlacklistedCountryIsos.isEmpty()) { valid = valid && !mBlacklistedCountryIsos.contains(iso); } return valid; } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); Bundle bundle = new Bundle(); bundle.putParcelable(KEY_SUPER_STATE, superState); bundle.putParcelable(KEY_COUNTRY_INFO, mSelectedCountryInfo); return bundle; } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof Bundle)) { super.onRestoreInstanceState(state); return; } Bundle bundle = (Bundle) state; Parcelable superState = bundle.getParcelable(KEY_SUPER_STATE); mSelectedCountryInfo = bundle.getParcelable(KEY_COUNTRY_INFO); super.onRestoreInstanceState(superState); } private static void hideKeyboard(Context context, View view) { final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } public void setSelectedForCountry(int countryCode, Locale locale) { setText(String.format(mTextFormat, CountryInfo.localeToEmoji(locale), countryCode)); mSelectedCountryInfo = new CountryInfo(locale, countryCode); } public void setSelectedForCountry(final Locale locale, String countryCode) { if (isValidIso(locale.getCountry())) { final String countryName = locale.getDisplayName(); if (!TextUtils.isEmpty(countryName) && !TextUtils.isEmpty(countryCode)) { mSelectedCountryName = countryName; setSelectedForCountry(Integer.parseInt(countryCode), locale); } } } public CountryInfo getSelectedCountryInfo() { return mSelectedCountryInfo; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mDialogPopup.isShowing()) { mDialogPopup.dismiss(); } } @Override public void setOnClickListener(OnClickListener l) { mListener = l; } @Override public void onClick(View view) { mDialogPopup.show(mCountryListAdapter.getPositionForCountry(mSelectedCountryName)); hideKeyboard(getContext(), this); executeUserClickListener(view); } private void executeUserClickListener(View view) { if (mListener != null) { mListener.onClick(view); } } public class DialogPopup implements DialogInterface.OnClickListener { //Delay for postDelayed to set selection without showing the scroll animation private static final long DELAY_MILLIS = 10L; private final CountryListAdapter listAdapter; private AlertDialog dialog; DialogPopup(CountryListAdapter adapter) { listAdapter = adapter; } public void dismiss() { if (dialog != null) { dialog.dismiss(); dialog = null; } } public boolean isShowing() { return dialog != null && dialog.isShowing(); } public void show(final int selected) { if (listAdapter == null) { return; } final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); dialog = builder.setSingleChoiceItems(listAdapter, 0, this).create(); dialog.setCanceledOnTouchOutside(true); final ListView listView = dialog.getListView(); listView.setFastScrollEnabled(true); listView.setScrollbarFadingEnabled(false); listView.postDelayed(new Runnable() { @Override public void run() { listView.setSelection(selected); } }, DELAY_MILLIS); dialog.show(); } @Override public void onClick(DialogInterface dialog, int which) { final CountryInfo countryInfo = listAdapter.getItem(which); mSelectedCountryName = countryInfo.getLocale().getDisplayCountry(); setSelectedForCountry(countryInfo.getCountryCode(), countryInfo.getLocale()); dismiss(); } } }