diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/Country.java b/app/src/main/java/org/thoughtcrime/securesms/delete/Country.java deleted file mode 100644 index bb83907baa..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/delete/Country.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.thoughtcrime.securesms.delete; - -import androidx.annotation.NonNull; - -import java.util.Objects; - -final class Country { - private final String displayName; - private final int code; - private final String normalized; - private final String region; - - Country(@NonNull String displayName, int code, @NonNull String region) { - this.displayName = displayName; - this.code = code; - this.normalized = displayName.toLowerCase(); - this.region = region; - } - - int getCode() { - return code; - } - - @NonNull String getDisplayName() { - return displayName; - } - - public String getNormalizedDisplayName() { - return normalized; - } - - @NonNull String getRegion() { - return region; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Country country = (Country) o; - return displayName.equals(country.displayName) && - code == country.code; - } - - @Override - public int hashCode() { - return Objects.hash(displayName, code); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryCodeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryCodeFragment.kt new file mode 100644 index 0000000000..f37a884ecf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryCodeFragment.kt @@ -0,0 +1,50 @@ +package org.thoughtcrime.securesms.delete + +import android.os.Bundle +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.stringResource +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.fragment.findNavController +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.compose.ComposeFragment +import org.thoughtcrime.securesms.registration.ui.countrycode.CountryCodeSelectScreen +import org.thoughtcrime.securesms.registration.ui.countrycode.CountryCodeViewModel + +/** + * Country code picker specific to deleting an account. + */ +class DeleteAccountCountryCodeFragment : ComposeFragment() { + + companion object { + const val RESULT_KEY = "result_key" + const val RESULT_COUNTRY = "result_country" + } + + private val viewModel: CountryCodeViewModel by viewModels() + + @Composable + override fun FragmentContent() { + val state by viewModel.state.collectAsStateWithLifecycle() + + CountryCodeSelectScreen( + state = state, + title = stringResource(R.string.CountryCodeFragment__your_country), + onSearch = { search -> viewModel.filterCountries(search) }, + onDismissed = { findNavController().popBackStack() }, + onClick = { country -> + setFragmentResult(RESULT_KEY, bundleOf(RESULT_COUNTRY to country)) + findNavController().popBackStack() + } + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.loadCountries() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryPickerAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryPickerAdapter.java deleted file mode 100644 index eafaa4f6f8..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryPickerAdapter.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.thoughtcrime.securesms.delete; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.util.Consumer; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.ListAdapter; -import androidx.recyclerview.widget.RecyclerView; - -import org.thoughtcrime.securesms.R; - -import java.util.Objects; - -class DeleteAccountCountryPickerAdapter extends ListAdapter { - - private final Callback callback; - - protected DeleteAccountCountryPickerAdapter(@NonNull Callback callback) { - super(new CountryDiffCallback()); - this.callback = callback; - } - - @Override - public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.delete_account_country_adapter_item, parent, false); - - return new ViewHolder(view, position -> callback.onItemSelected(getItem(position))); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - holder.textView.setText(getItem(position).getDisplayName()); - } - - static class ViewHolder extends RecyclerView.ViewHolder { - - private final TextView textView; - - public ViewHolder(@NonNull View itemView, @NonNull Consumer onItemClickedConsumer) { - super(itemView); - textView = itemView.findViewById(android.R.id.text1); - - itemView.setOnClickListener(unused -> { - if (getAdapterPosition() != RecyclerView.NO_POSITION) { - onItemClickedConsumer.accept(getAdapterPosition()); - } - }); - } - } - - private static class CountryDiffCallback extends DiffUtil.ItemCallback { - - @Override - public boolean areItemsTheSame(@NonNull Country oldItem, @NonNull Country newItem) { - return Objects.equals(oldItem.getCode(), newItem.getCode()); - } - - @Override - public boolean areContentsTheSame(@NonNull Country oldItem, @NonNull Country newItem) { - return Objects.equals(oldItem, newItem); - } - } - - interface Callback { - void onItemSelected(@NonNull Country country); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryPickerFragment.java b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryPickerFragment.java deleted file mode 100644 index c10240c9ce..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountCountryPickerFragment.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.thoughtcrime.securesms.delete; - -import android.os.Bundle; -import android.text.Editable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentManager; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.RecyclerView; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.util.text.AfterTextChanged; - -public class DeleteAccountCountryPickerFragment extends DialogFragment { - - private DeleteAccountViewModel viewModel; - - public static void show(@NonNull FragmentManager fragmentManager) { - new DeleteAccountCountryPickerFragment().show(fragmentManager, null); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen); - } - - @Override - public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.delete_account_country_picker, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - Toolbar toolbar = view.findViewById(R.id.delete_account_country_picker_toolbar); - EditText searchFilter = view.findViewById(R.id.delete_account_country_picker_filter); - RecyclerView recycler = view.findViewById(R.id.delete_account_country_picker_recycler); - DeleteAccountCountryPickerAdapter adapter = new DeleteAccountCountryPickerAdapter(this::onCountryPicked); - - recycler.setAdapter(adapter); - - toolbar.setNavigationOnClickListener(unused -> dismiss()); - - viewModel = new ViewModelProvider(requireActivity()).get(DeleteAccountViewModel.class); - viewModel.getFilteredCountries().observe(getViewLifecycleOwner(), adapter::submitList); - - searchFilter.addTextChangedListener(new AfterTextChanged(this::onQueryChanged)); - } - - private void onQueryChanged(@NonNull Editable e) { - viewModel.onQueryChanged(e.toString()); - } - - private void onCountryPicked(@NonNull Country country) { - viewModel.onRegionSelected(country.getRegion()); - dismissAllowingStateLoss(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java index e8c5c9924f..528cb7169c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java @@ -1,25 +1,18 @@ package org.thoughtcrime.securesms.delete; -import android.annotation.SuppressLint; -import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; -import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextUtils; -import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.ArrayAdapter; import android.widget.EditText; -import android.widget.Spinner; import android.widget.TextView; import androidx.annotation.NonNull; @@ -28,6 +21,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.Navigation; +import androidx.navigation.fragment.NavHostFragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; @@ -36,8 +30,10 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.LabeledEditText; +import org.thoughtcrime.securesms.registration.ui.countrycode.Country; import org.thoughtcrime.securesms.util.SpanUtil; import org.thoughtcrime.securesms.util.ViewUtil; +import org.thoughtcrime.securesms.util.navigation.SafeNavigation; import org.thoughtcrime.securesms.util.text.AfterTextChanged; import java.util.Optional; @@ -45,7 +41,7 @@ import java.util.Optional; public class DeleteAccountFragment extends Fragment { - private ArrayAdapter countrySpinnerAdapter; + private TextView countryPicker; private TextView bullets; private LabeledEditText countryCode; private LabeledEditText number; @@ -60,13 +56,13 @@ public class DeleteAccountFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - Spinner countrySpinner = view.findViewById(R.id.delete_account_fragment_country_spinner); View confirm = view.findViewById(R.id.delete_account_fragment_delete); Toolbar toolbar = view.findViewById(R.id.toolbar); - bullets = view.findViewById(R.id.delete_account_fragment_bullets); - countryCode = view.findViewById(R.id.delete_account_fragment_country_code); - number = view.findViewById(R.id.delete_account_fragment_number); + bullets = view.findViewById(R.id.delete_account_fragment_bullets); + countryCode = view.findViewById(R.id.delete_account_fragment_country_code); + number = view.findViewById(R.id.delete_account_fragment_number); + countryPicker = view.findViewById(R.id.delete_account_fragment_country_picker); viewModel = new ViewModelProvider(requireActivity(), new DeleteAccountViewModel.Factory(new DeleteAccountRepository())).get(DeleteAccountViewModel.class); viewModel.getCountryDisplayName().observe(getViewLifecycleOwner(), this::setCountryDisplay); @@ -80,8 +76,14 @@ public class DeleteAccountFragment extends Fragment { countryCode.getInput().setImeOptions(EditorInfo.IME_ACTION_NEXT); confirm.setOnClickListener(unused -> viewModel.submit()); toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(v).popBackStack()); + countryPicker.setOnClickListener(v -> SafeNavigation.safeNavigate(NavHostFragment.findNavController(this), R.id.action_deleteAccountFragment_to_deleteAccountCountryFragment)); - initializeSpinner(countrySpinner); + getParentFragmentManager().setFragmentResultListener(DeleteAccountCountryCodeFragment.RESULT_KEY, this, (key, bundle) -> { + Country country = bundle.getParcelable(DeleteAccountCountryCodeFragment.RESULT_COUNTRY); + if (country != null) { + viewModel.onRegionSelected(country.getRegionCode()); + } + }); } private void updateBullets(@NonNull Optional formattedBalance) { @@ -101,38 +103,11 @@ public class DeleteAccountFragment extends Fragment { return builder; } - @SuppressLint("ClickableViewAccessibility") - private void initializeSpinner(@NonNull Spinner countrySpinner) { - countrySpinnerAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item); - countrySpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - countrySpinner.setAdapter(countrySpinnerAdapter); - countrySpinner.setOnTouchListener((view, event) -> { - if (event.getAction() == MotionEvent.ACTION_UP) { - pickCountry(); - } - return true; - }); - countrySpinner.setOnKeyListener((view, keyCode, event) -> { - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && event.getAction() == KeyEvent.ACTION_UP) { - pickCountry(); - return true; - } - return false; - }); - } - - private void pickCountry() { - countryCode.clearFocus(); - DeleteAccountCountryPickerFragment.show(requireFragmentManager()); - } - private void setCountryDisplay(@NonNull String regionDisplayName) { - countrySpinnerAdapter.clear(); if (TextUtils.isEmpty(regionDisplayName)) { - countrySpinnerAdapter.add(requireContext().getString(R.string.RegistrationActivity_select_your_country)); + countryPicker.setText(requireContext().getString(R.string.RegistrationActivity_select_your_country)); } else { - countrySpinnerAdapter.add(regionDisplayName); + countryPicker.setText(regionDisplayName); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountRepository.java b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountRepository.java index 8737c5e2d2..6571ed6377 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountRepository.java @@ -24,20 +24,10 @@ import org.whispersystems.signalservice.internal.EmptyResponse; import org.whispersystems.signalservice.internal.ServiceResponse; import java.io.IOException; -import java.text.Collator; -import java.util.Comparator; -import java.util.List; class DeleteAccountRepository { private static final String TAG = Log.tag(DeleteAccountRepository.class); - @NonNull List getAllCountries() { - return Stream.of(PhoneNumberUtil.getInstance().getSupportedRegions()) - .map(DeleteAccountRepository::getCountryForRegion) - .sorted(new RegionComparator()) - .toList(); - } - @NonNull String getRegionDisplayName(@NonNull String region) { return E164Util.getRegionDisplayName(region).orElse(""); } @@ -126,25 +116,4 @@ class DeleteAccountRepository { } }); } - - private static @NonNull Country getCountryForRegion(@NonNull String region) { - return new Country(E164Util.getRegionDisplayName(region).orElse(""), - PhoneNumberUtil.getInstance().getCountryCodeForRegion(region), - region); - } - - private static class RegionComparator implements Comparator { - - private final Collator collator; - - RegionComparator() { - collator = Collator.getInstance(); - collator.setStrength(Collator.PRIMARY); - } - - @Override - public int compare(Country lhs, Country rhs) { - return collator.compare(lhs.getDisplayName(), rhs.getDisplayName()); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountViewModel.java index 6b92a441f8..62aeecb9aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountViewModel.java @@ -29,23 +29,17 @@ import java.util.Optional; public class DeleteAccountViewModel extends ViewModel { private final DeleteAccountRepository repository; - private final List allCountries; - private final LiveData> filteredCountries; private final MutableLiveData regionCode; private final LiveData countryDisplayName; private final MutableLiveData nationalNumber; - private final MutableLiveData query; private final SingleLiveEvent events; private final LiveData> walletBalance; public DeleteAccountViewModel(@NonNull DeleteAccountRepository repository) { this.repository = repository; - this.allCountries = repository.getAllCountries(); this.regionCode = new DefaultValueLiveData<>("ZZ"); // PhoneNumberUtil private static final String UNKNOWN_REGION = "ZZ"; this.nationalNumber = new MutableLiveData<>(); - this.query = new DefaultValueLiveData<>(""); this.countryDisplayName = Transformations.map(regionCode, repository::getRegionDisplayName); - this.filteredCountries = Transformations.map(query, q -> Stream.of(allCountries).filter(country -> isMatch(q, country)).toList()); this.events = new SingleLiveEvent<>(); this.walletBalance = Transformations.map(SignalStore.payments().liveMobileCoinBalance(), DeleteAccountViewModel::getFormattedWalletBalance); @@ -55,10 +49,6 @@ public class DeleteAccountViewModel extends ViewModel { return walletBalance; } - @NonNull LiveData> getFilteredCountries() { - return filteredCountries; - } - @NonNull LiveData getCountryDisplayName() { return Transformations.distinctUntilChanged(countryDisplayName); } @@ -75,10 +65,6 @@ public class DeleteAccountViewModel extends ViewModel { return nationalNumber.getValue(); } - void onQueryChanged(@NonNull String query) { - this.query.setValue(query.toLowerCase()); - } - void deleteAccount() { repository.deleteAccount(events::postValue); } @@ -146,14 +132,6 @@ public class DeleteAccountViewModel extends ViewModel { } } - private static boolean isMatch(@NonNull String query, @NonNull Country country) { - if (TextUtils.isEmpty(query)) { - return true; - } else { - return country.getNormalizedDisplayName().contains(query.toLowerCase()); - } - } - public static final class Factory implements ViewModelProvider.Factory { private final DeleteAccountRepository repository; diff --git a/app/src/main/res/layout/delete_account_fragment.xml b/app/src/main/res/layout/delete_account_fragment.xml index c00f3c316c..89ceb1a497 100644 --- a/app/src/main/res/layout/delete_account_fragment.xml +++ b/app/src/main/res/layout/delete_account_fragment.xml @@ -83,11 +83,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/delete_account_fragment_enter_phone_number"> - diff --git a/app/src/main/res/navigation/app_settings_with_change_number.xml b/app/src/main/res/navigation/app_settings_with_change_number.xml index 73c9823922..50dcac816f 100644 --- a/app/src/main/res/navigation/app_settings_with_change_number.xml +++ b/app/src/main/res/navigation/app_settings_with_change_number.xml @@ -223,7 +223,21 @@ android:id="@+id/deleteAccountFragment" android:name="org.thoughtcrime.securesms.delete.DeleteAccountFragment" android:label="delete_account_fragment" - tools:layout="@layout/delete_account_fragment" /> + tools:layout="@layout/delete_account_fragment"> + + + + + +