From c52f35bc3e21e7df416f6d3c31aa579a359d59ad Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sat, 12 Sep 2020 21:39:43 +0900 Subject: [PATCH] Convert AccountSwitcher dialog to a DialogFragment as the AlertDialog was causing a leak (detected by LeakCanary) --- ...apter.java => AccountSwitcherAdapter.java} | 64 ++++---- .../AccountSwitcherDialogFragment.java | 152 ++++++++++++++++++ .../settings/MorePreferencesFragment.java | 111 ++----------- .../res/layout/dialog_account_switcher.xml | 29 ++++ 4 files changed, 224 insertions(+), 132 deletions(-) rename app/src/main/java/awais/instagrabber/adapters/{AccountSwitcherListAdapter.java => AccountSwitcherAdapter.java} (61%) create mode 100644 app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java create mode 100644 app/src/main/res/layout/dialog_account_switcher.xml diff --git a/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherListAdapter.java b/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherAdapter.java similarity index 61% rename from app/src/main/java/awais/instagrabber/adapters/AccountSwitcherListAdapter.java rename to app/src/main/java/awais/instagrabber/adapters/AccountSwitcherAdapter.java index daa66f44..8e984ddc 100644 --- a/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherListAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherAdapter.java @@ -1,18 +1,16 @@ package awais.instagrabber.adapters; import android.annotation.SuppressLint; -import android.content.Context; import android.graphics.Typeface; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.List; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; import awais.instagrabber.R; import awais.instagrabber.databinding.PrefAccountSwitcherBinding; @@ -21,42 +19,45 @@ import awais.instagrabber.utils.DataBox; import static awais.instagrabber.utils.Utils.settingsHelper; -public class AccountSwitcherListAdapter extends ArrayAdapter { - private static final String TAG = "AccountSwitcherListAdap"; +public class AccountSwitcherAdapter extends ListAdapter { + private static final String TAG = "AccountSwitcherAdapter"; + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final DataBox.CookieModel oldItem, @NonNull final DataBox.CookieModel newItem) { + return oldItem.getUid().equals(newItem.getUid()); + } + + @Override + public boolean areContentsTheSame(@NonNull final DataBox.CookieModel oldItem, @NonNull final DataBox.CookieModel newItem) { + return oldItem.getUid().equals(newItem.getUid()); + } + }; private final OnAccountClickListener clickListener; private final OnAccountLongClickListener longClickListener; - public AccountSwitcherListAdapter(@NonNull final Context context, - final int resource, - @NonNull final List allUsers, - final OnAccountClickListener clickListener, - final OnAccountLongClickListener longClickListener) { - super(context, resource, allUsers); + public AccountSwitcherAdapter(final OnAccountClickListener clickListener, + final OnAccountLongClickListener longClickListener) { + super(DIFF_CALLBACK); this.clickListener = clickListener; this.longClickListener = longClickListener; } @NonNull @Override - public View getView(final int position, @Nullable final View convertView, @NonNull final ViewGroup parent) { + public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + final PrefAccountSwitcherBinding binding = PrefAccountSwitcherBinding.inflate(layoutInflater, parent, false); + return new ViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) { final DataBox.CookieModel model = getItem(position); + if (model == null) return; final String cookie = settingsHelper.getString(Constants.COOKIE); - if (convertView == null) { - final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - final PrefAccountSwitcherBinding binding = PrefAccountSwitcherBinding.inflate(layoutInflater, parent, false); - final ViewHolder viewHolder = new ViewHolder(binding); - viewHolder.itemView.setTag(viewHolder); - if (model == null) return viewHolder.itemView; - final boolean equals = model.getCookie().equals(cookie); - viewHolder.bind(model, equals, clickListener, longClickListener); - return viewHolder.itemView; - } - final ViewHolder viewHolder = (ViewHolder) convertView.getTag(); - if (model == null) return viewHolder.itemView; - final boolean equals = model.getCookie().equals(cookie); - viewHolder.bind(model, equals, clickListener, longClickListener); - return viewHolder.itemView; + final boolean isCurrent = model.getCookie().equals(cookie); + holder.bind(model, isCurrent, clickListener, longClickListener); } public interface OnAccountClickListener { @@ -67,12 +68,11 @@ public class AccountSwitcherListAdapter extends ArrayAdapter { + if (isCurrent) { + dismiss(); + return; + } + CookieUtils.setupCookies(model.getCookie()); + settingsHelper.putString(Constants.COOKIE, model.getCookie()); + final FragmentActivity activity = getActivity(); + if (activity != null) activity.recreate(); + dismiss(); + }; + + private final AccountSwitcherAdapter.OnAccountLongClickListener accountLongClickListener = (model, isCurrent) -> { + final Context context = getContext(); + if (context == null) return false; + if (isCurrent) { + new AlertDialog.Builder(context) + .setMessage(R.string.quick_access_cannot_delete_curr) + .setPositiveButton(R.string.ok, null) + .show(); + return true; + } + new AlertDialog.Builder(context) + .setMessage(getString(R.string.quick_access_confirm_delete, model.getUsername())) + .setPositiveButton(R.string.yes, (dialog, which) -> { + Utils.dataBox.delUserCookie(model); + dismiss(); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + dismiss(); + return true; + }; + + public AccountSwitcherDialogFragment(final OnAddAccountClickListener onAddAccountClickListener) { + this.onAddAccountClickListener = onAddAccountClickListener; + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + final ViewGroup container, + final Bundle savedInstanceState) { + binding = DialogAccountSwitcherBinding.inflate(inflater, container, false); + binding.accounts.setLayoutManager(new LinearLayoutManager(getContext())); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + init(); + } + + @Override + public void onStart() { + super.onStart(); + final Dialog dialog = getDialog(); + if (dialog == null) return; + final Window window = dialog.getWindow(); + if (window == null) return; + final int height = ViewGroup.LayoutParams.WRAP_CONTENT; + final int width = (int) (Utils.displayMetrics.widthPixels * 0.8); + window.setLayout(width, height); + } + + private void init() { + final AccountSwitcherAdapter adapter = new AccountSwitcherAdapter(accountClickListener, accountLongClickListener); + binding.accounts.setAdapter(adapter); + final List allUsers = Utils.dataBox.getAllCookies(); + if (allUsers == null) return; + final String cookie = settingsHelper.getString(Constants.COOKIE); + sortUserList(cookie, allUsers); + adapter.submitList(allUsers); + binding.addAccountBtn.setOnClickListener(v -> { + if (onAddAccountClickListener == null) return; + onAddAccountClickListener.onAddAccountClick(this); + }); + } + + /** + * Sort the user list by following logic: + *
    + *
  1. Keep currently active account at top. + *
  2. Check if any user does not have a full name. + *
  3. If all have full names, sort by full names. + *
  4. Otherwise, sort by the usernames + *
+ * + * @param cookie active cookie + * @param allUsers list of users + */ + private void sortUserList(final String cookie, final List allUsers) { + boolean sortByName = true; + for (final DataBox.CookieModel user : allUsers) { + if (TextUtils.isEmpty(user.getFullName())) { + sortByName = false; + break; + } + } + final boolean finalSortByName = sortByName; + Collections.sort(allUsers, (o1, o2) -> { + // keep current account at top + if (o1.getCookie().equals(cookie)) return -1; + if (finalSortByName) { + // sort by full name + return o1.getFullName().compareTo(o2.getFullName()); + } + // otherwise sort by username + return o1.getUsername().compareTo(o2.getUsername()); + }); + } + + public interface OnAddAccountClickListener { + void onAddAccountClick(final AccountSwitcherDialogFragment dialogFragment); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 2b7c45dc..2adfab73 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -6,13 +6,13 @@ import android.content.Intent; import android.content.res.Resources; import android.util.Log; import android.view.View; -import android.widget.ArrayAdapter; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.preference.Preference; @@ -21,15 +21,12 @@ import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.activities.Login; -import awais.instagrabber.adapters.AccountSwitcherListAdapter; -import awais.instagrabber.adapters.AccountSwitcherListAdapter.OnAccountClickListener; import awais.instagrabber.databinding.PrefAccountSwitcherBinding; +import awais.instagrabber.dialogs.AccountSwitcherDialogFragment; import awais.instagrabber.repositories.responses.UserInfo; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; @@ -40,14 +37,11 @@ import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.ProfileService; import awais.instagrabber.webservices.ServiceCallback; -import static awais.instagrabber.adapters.AccountSwitcherListAdapter.OnAccountLongClickListener; import static awais.instagrabber.utils.Utils.settingsHelper; public class MorePreferencesFragment extends BasePreferencesFragment { private static final String TAG = "MorePreferencesFragment"; - private AlertDialog accountSwitchDialog; - private DataBox.CookieModel tappedModel; - private ArrayAdapter adapter; + @Override void setupPreferenceScreen(final PreferenceScreen screen) { @@ -175,102 +169,18 @@ public class MorePreferencesFragment extends BasePreferencesFragment { } private AccountSwitcherPreference getAccountSwitcherPreference(final String cookie) { - final List allUsers = Utils.dataBox.getAllCookies(); - if (getContext() != null && allUsers != null) { - sortUserList(cookie, allUsers); - final OnAccountClickListener clickListener = (model, isCurrent) -> { - if (isCurrent) { - if (accountSwitchDialog == null) return; - accountSwitchDialog.dismiss(); - return; - } - tappedModel = model; - shouldRecreate(); - if (accountSwitchDialog == null) return; - accountSwitchDialog.dismiss(); - }; - final OnAccountLongClickListener longClickListener = (model, isCurrent) -> { - if (isCurrent) { - new AlertDialog.Builder(getContext()) - .setMessage(R.string.quick_access_cannot_delete_curr) - .setPositiveButton(R.string.ok, null) - .show(); - return true; - } - new AlertDialog.Builder(getContext()) - .setMessage(getString(R.string.quick_access_confirm_delete, model.getUsername())) - .setPositiveButton(R.string.yes, (dialog, which) -> { - Utils.dataBox.delUserCookie(model); - adapter.clear(); - final List users = Utils.dataBox.getAllCookies(); - if (users == null) return; - adapter.addAll(users); - }) - .setNegativeButton(R.string.cancel, null) - .show(); - accountSwitchDialog.dismiss(); - return true; - }; - adapter = new AccountSwitcherListAdapter( - getContext(), - R.layout.pref_account_switcher, - allUsers, - clickListener, - longClickListener - ); - accountSwitchDialog = new AlertDialog.Builder(getContext()) - .setTitle("Accounts") - .setNeutralButton("Add account", (dialog1, which) -> startActivityForResult( - new Intent(getContext(), Login.class), - Constants.LOGIN_RESULT_CODE)) - .setAdapter(adapter, null) - .create(); - accountSwitchDialog.setOnDismissListener(dialog -> { - if (tappedModel == null) return; - CookieUtils.setupCookies(tappedModel.getCookie()); - settingsHelper.putString(Constants.COOKIE, tappedModel.getCookie()); - }); - } - final AlertDialog finalDialog = accountSwitchDialog; final Context context = getContext(); if (context == null) return null; - return new AccountSwitcherPreference(context, cookie, v -> { - if (finalDialog == null) return; - finalDialog.show(); - }); + return new AccountSwitcherPreference(context, cookie, v -> showAccountSwitcherDialog()); } - /** - * Sort the user list by following logic: - *
    - *
  1. Keep currently active account at top. - *
  2. Check if any user does not have a full name. - *
  3. If all have full names, sort by full names. - *
  4. Otherwise, sort by the usernames - *
- * - * @param cookie active cookie - * @param allUsers list of users - */ - private void sortUserList(final String cookie, final List allUsers) { - boolean sortByName = true; - for (final DataBox.CookieModel user : allUsers) { - if (TextUtils.isEmpty(user.getFullName())) { - sortByName = false; - break; - } - } - final boolean finalSortByName = sortByName; - Collections.sort(allUsers, (o1, o2) -> { - // keep current account at top - if (o1.getCookie().equals(cookie)) return -1; - if (finalSortByName) { - // sort by full name - return o1.getFullName().compareTo(o2.getFullName()); - } - // otherwise sort by username - return o1.getUsername().compareTo(o2.getUsername()); + private void showAccountSwitcherDialog() { + final AccountSwitcherDialogFragment dialogFragment = new AccountSwitcherDialogFragment(dialog -> { + dialog.dismiss(); + startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE); }); + final FragmentManager fragmentManager = getChildFragmentManager(); + dialogFragment.show(fragmentManager, "accountSwitcher"); } private Preference getPreference(final int title, @@ -311,6 +221,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment { return preference; } + public static class MoreHeaderPreference extends Preference { public MoreHeaderPreference(final Context context) { diff --git a/app/src/main/res/layout/dialog_account_switcher.xml b/app/src/main/res/layout/dialog_account_switcher.xml new file mode 100644 index 00000000..7070a14a --- /dev/null +++ b/app/src/main/res/layout/dialog_account_switcher.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file