From 5c8c888024ab1607b8af5ff9ca75fd3bd4e2dbc6 Mon Sep 17 00:00:00 2001 From: sk Date: Mon, 23 Jan 2023 06:51:24 -0300 Subject: [PATCH] fix akkoma crash on list edit closes sk22#352 --- .../android/fragments/HomeTabFragment.java | 2 +- .../fragments/ListTimelineFragment.java | 59 ++- .../fragments/ListTimelinesFragment.java | 375 +++++++++--------- .../android/model/ListTimeline.java | 4 +- .../android/ui/views/ListTimelineEditor.java | 9 +- 5 files changed, 221 insertions(+), 228 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java index ad870fd8b..957b8b534 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java @@ -423,7 +423,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab } else if ((list = listItems.get(id)) != null) { args.putString("listID", list.id); args.putString("listTitle", list.title); - args.putInt("repliesPolicy", list.repliesPolicy.ordinal()); + if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal()); Nav.go(getActivity(), ListTimelineFragment.class, args); } else if ((hashtag = hashtagsItems.get(id)) != null) { args.putString("hashtag", hashtag.name); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java index 156123e02..99cfa4021 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java @@ -2,16 +2,15 @@ package org.joinmastodon.android.fragments; import android.app.Activity; import android.os.Bundle; -import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; -import android.widget.Toast; -import org.joinmastodon.android.GlobalUserPreferences; +import androidx.annotation.Nullable; + import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.lists.GetList; import org.joinmastodon.android.api.requests.lists.UpdateList; @@ -23,7 +22,6 @@ import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.ListTimelineEditor; -import java.util.ArrayList; import java.util.List; import me.grishka.appkit.Nav; @@ -36,9 +34,9 @@ import me.grishka.appkit.utils.V; public class ListTimelineFragment extends PinnableStatusListFragment { private String listID; private String listTitle; + @Nullable private ListTimeline.RepliesPolicy repliesPolicy; private ImageButton fab; - private Bundle resultArgs = new Bundle(); public ListTimelineFragment() { setListLayoutId(R.layout.recycler_fragment_with_fab); @@ -51,7 +49,6 @@ public class ListTimelineFragment extends PinnableStatusListFragment { listID = args.getString("listID"); listTitle = args.getString("listTitle"); repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)]; - resultArgs.putString("listID", listID); setTitle(listTitle); setHasOptionsMenu(true); @@ -61,7 +58,9 @@ public class ListTimelineFragment extends PinnableStatusListFragment { public void onSuccess(ListTimeline listTimeline) { // TODO: save updated info if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title); - if (!listTimeline.repliesPolicy.equals(repliesPolicy)) repliesPolicy = listTimeline.repliesPolicy; + if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) { + repliesPolicy = listTimeline.repliesPolicy; + } } @Override @@ -88,41 +87,39 @@ public class ListTimelineFragment extends PinnableStatusListFragment { .setTitle(R.string.sk_edit_list_title) .setIcon(R.drawable.ic_fluent_people_list_28_regular) .setView(editor) - .setPositiveButton(R.string.save, (d, which) -> { - new UpdateList(listID, editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() { - @Override - public void onSuccess(ListTimeline list) { - setTitle(list.title); - listTitle = list.title; - repliesPolicy = list.repliesPolicy; - resultArgs.putString("listTitle", listTitle); - resultArgs.putInt("repliesPolicy", repliesPolicy.ordinal()); - setResult(true, resultArgs); - } + .setPositiveButton(R.string.save, (d, which) -> + new UpdateList(listID, editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() { + @Override + public void onSuccess(ListTimeline list) { + setTitle(list.title); + listTitle = list.title; + repliesPolicy = list.repliesPolicy; + Bundle result = new Bundle(); + result.putString("listID", listID); + result.putString("listTitle", listTitle); + if (repliesPolicy != null) result.putInt("repliesPolicy", repliesPolicy.ordinal()); + setResult(true, result); + } - @Override - public void onError(ErrorResponse error) { - error.showToast(getContext()); - } - }).exec(accountID); - }) + @Override + public void onError(ErrorResponse error) { + error.showToast(getContext()); + } + }).exec(accountID)) .setNegativeButton(R.string.cancel, (d, which) -> {}) .show(); } else if (item.getItemId() == R.id.delete) { UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> { - resultArgs.putBoolean("deleted", true); - setResult(true, resultArgs); + Bundle result = new Bundle(); + result.putBoolean("deleted", true); + result.putString("listID", listID); + setResult(true, result); Nav.finish(this); }); } return true; } - @Override - public Bundle getResultArgs() { - return resultArgs; - } - @Override protected TimelineDefinition makeTimelineDefinition() { return TimelineDefinition.ofList(listID, listTitle); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java index 4eb83b807..221d46304 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java @@ -21,7 +21,6 @@ import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList; import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.M3AlertDialogBuilder; -import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.ListTimelineEditor; import java.util.ArrayList; @@ -38,218 +37,210 @@ import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.views.UsableRecyclerView; public class ListTimelinesFragment extends BaseRecyclerFragment implements ScrollableToTop { - private static final int LIST_CHANGED_RESULT = 987; + private static final int LIST_CHANGED_RESULT = 987; - private String accountId; - private String profileAccountId; - private String profileDisplayUsername; - private HashMap userInListBefore = new HashMap<>(); - private HashMap userInList = new HashMap<>(); - private int inProgress = 0; - private ListsAdapter adapter; - private boolean pinnedUpdated; + private String accountId; + private String profileAccountId; + private final HashMap userInListBefore = new HashMap<>(); + private final HashMap userInList = new HashMap<>(); + private ListsAdapter adapter; - public ListTimelinesFragment() { - super(10); - } + public ListTimelinesFragment() { + super(10); + } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args=getArguments(); - accountId=args.getString("account"); - setHasOptionsMenu(true); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle args=getArguments(); + accountId=args.getString("account"); + setHasOptionsMenu(true); - if(args.containsKey("profileAccount")){ - profileAccountId=args.getString("profileAccount"); - profileDisplayUsername=args.getString("profileDisplayUsername"); - setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername)); - } else { - setTitle(R.string.sk_your_lists); - } - } + if(args.containsKey("profileAccount")){ + profileAccountId=args.getString("profileAccount"); + String profileDisplayUsername = args.getString("profileDisplayUsername"); + setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername)); + } else { + setTitle(R.string.sk_your_lists); + } + } - @Override - protected void onShown(){ - super.onShown(); - if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) - loadData(); - } + @Override + protected void onShown(){ + super.onShown(); + if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) + loadData(); + } - @Override - public void onDestroy() { - super.onDestroy(); - if (pinnedUpdated) UiUtils.restartApp(); - } + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16)); + } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16)); - } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.menu_list, menu); + } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.menu_list, menu); - } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.create) { + ListTimelineEditor editor = new ListTimelineEditor(getContext()); + new M3AlertDialogBuilder(getActivity()) + .setTitle(R.string.sk_create_list_title) + .setIcon(R.drawable.ic_fluent_people_add_28_regular) + .setView(editor) + .setPositiveButton(R.string.sk_create, (d, which) -> + new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() { + @Override + public void onSuccess(ListTimeline list) { + saveListMembership(list.id, true); + data.add(0, list); + adapter.notifyItemRangeInserted(0, 1); + } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.create) { - ListTimelineEditor editor = new ListTimelineEditor(getContext()); - new M3AlertDialogBuilder(getActivity()) - .setTitle(R.string.sk_create_list_title) - .setIcon(R.drawable.ic_fluent_people_add_28_regular) - .setView(editor) - .setPositiveButton(R.string.sk_create, (d, which) -> { - new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() { - @Override - public void onSuccess(ListTimeline list) { - saveListMembership(list.id, true); - data.add(0, list); - adapter.notifyItemRangeInserted(0, 1); - } + @Override + public void onError(ErrorResponse error) { + error.showToast(getContext()); + } + }).exec(accountId) + ) + .setNegativeButton(R.string.cancel, (d, which) -> {}) + .show(); + } + return true; + } - @Override - public void onError(ErrorResponse error) { - error.showToast(getContext()); - } - }).exec(accountId); - }) - .setNegativeButton(R.string.cancel, (d, which) -> {}) - .show(); - } - return true; - } + private void saveListMembership(String listId, boolean isMember) { + userInList.put(listId, isMember); + List accountIdList = Collections.singletonList(profileAccountId); + MastodonAPIRequest req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList); + req.setCallback(new SimpleCallback<>(this) { + @Override + public void onSuccess(Object o) {} + }).exec(accountId); + } - private void saveListMembership(String listId, boolean isMember) { - userInList.put(listId, isMember); - List accountIdList = Collections.singletonList(profileAccountId); - MastodonAPIRequest req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList); - req.setCallback(new SimpleCallback<>(this) { - @Override - public void onSuccess(Object o) {} - }).exec(accountId); - } + @Override + protected void doLoadData(int offset, int count){ + userInListBefore.clear(); + userInList.clear(); + currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists()) + .setCallback(new SimpleCallback<>(this) { + @Override + public void onSuccess(List lists) { + for (ListTimeline l : lists) userInListBefore.put(l.id, true); + userInList.putAll(userInListBefore); + if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false); + if (profileAccountId == null) return; - @Override - protected void doLoadData(int offset, int count){ - userInListBefore.clear(); - userInList.clear(); - currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists()) - .setCallback(new SimpleCallback<>(this) { - @Override - public void onSuccess(List lists) { - for (ListTimeline l : lists) userInListBefore.put(l.id, true); - userInList.putAll(userInListBefore); - if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false); - if (profileAccountId == null) return; + currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) { + @Override + public void onSuccess(List allLists) { + List newLists = new ArrayList<>(); + for (ListTimeline l : allLists) { + if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l); + if (!userInListBefore.containsKey(l.id)) { + userInListBefore.put(l.id, false); + } + } + userInList.putAll(userInListBefore); + onDataLoaded(newLists, false); + } + }).exec(accountId); + } + }) + .exec(accountId); + } - currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) { - @Override - public void onSuccess(List allLists) { - List newLists = new ArrayList<>(); - for (ListTimeline l : allLists) { - if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l); - if (!userInListBefore.containsKey(l.id)) { - userInListBefore.put(l.id, false); - } - } - userInList.putAll(userInListBefore); - onDataLoaded(newLists, false); - } - }).exec(accountId); - } - }) - .exec(accountId); - } + @Override + public void onFragmentResult(int reqCode, boolean listChanged, Bundle result){ + if (reqCode == LIST_CHANGED_RESULT && listChanged) { + String listID = result.getString("listID"); + for (int i = 0; i < data.size(); i++) { + ListTimeline item = data.get(i); + if (item.id.equals(listID)) { + if (result.getBoolean("deleted")) { + data.remove(i); + adapter.notifyItemRemoved(i); + } else { + item.title = result.getString("listTitle", item.title); + if (result.containsKey("repliesPolicy")) { + item.repliesPolicy = ListTimeline.RepliesPolicy.values()[result.getInt("repliesPolicy")]; + } + adapter.notifyItemChanged(i); + } + break; + } + } + } + } - @Override - public void onFragmentResult(int reqCode, boolean listChanged, Bundle result){ - if (reqCode == LIST_CHANGED_RESULT && listChanged) { - if (result.getBoolean("pinnedUpdated")) pinnedUpdated = true; - String listID = result.getString("listID"); - for (int i = 0; i < data.size(); i++) { - ListTimeline item = data.get(i); - if (item.id.equals(listID)) { - if (result.getBoolean("deleted")) { - data.remove(i); - adapter.notifyItemRemoved(i); - } else { - item.title = result.getString("listTitle", item.title); - item.repliesPolicy = ListTimeline.RepliesPolicy.values()[result.getInt("repliesPolicy")]; - adapter.notifyItemChanged(i); - } - break; - } - } - } - } + @Override + protected RecyclerView.Adapter getAdapter() { + return adapter = new ListsAdapter(); + } - @Override - protected RecyclerView.Adapter getAdapter() { - return adapter = new ListsAdapter(); - } + @Override + public void scrollToTop() { + smoothScrollRecyclerViewToTop(list); + } - @Override - public void scrollToTop() { - smoothScrollRecyclerViewToTop(list); - } + private class ListsAdapter extends RecyclerView.Adapter{ + @NonNull + @Override + public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new ListViewHolder(); + } - private class ListsAdapter extends RecyclerView.Adapter{ - @NonNull - @Override - public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ - return new ListViewHolder(); - } + @Override + public void onBindViewHolder(@NonNull ListViewHolder holder, int position) { + holder.bind(data.get(position)); + } - @Override - public void onBindViewHolder(@NonNull ListViewHolder holder, int position) { - holder.bind(data.get(position)); - } + @Override + public int getItemCount() { + return data.size(); + } + } - @Override - public int getItemCount() { - return data.size(); - } - } + private class ListViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ + private final TextView title; + private final CheckBox listToggle; - private class ListViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ - private final TextView title; - private final CheckBox listToggle; + public ListViewHolder(){ + super(getActivity(), R.layout.item_text, list); + title=findViewById(R.id.title); + listToggle=findViewById(R.id.list_toggle); + } - public ListViewHolder(){ - super(getActivity(), R.layout.item_text, list); - title=findViewById(R.id.title); - listToggle=findViewById(R.id.list_toggle); - } + @Override + public void onBind(ListTimeline item) { + title.setText(item.title); + title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_list_24_regular), null, null, null); + if (profileAccountId != null) { + Boolean checked = userInList.get(item.id); + listToggle.setVisibility(View.VISIBLE); + listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked); + listToggle.setOnClickListener(this::onClickToggle); + } else { + listToggle.setVisibility(View.GONE); + } + } - @Override - public void onBind(ListTimeline item) { - title.setText(item.title); - title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_list_24_regular), null, null, null); - if (profileAccountId != null) { - Boolean checked = userInList.get(item.id); - listToggle.setVisibility(View.VISIBLE); - listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked); - listToggle.setOnClickListener(this::onClickToggle); - } else { - listToggle.setVisibility(View.GONE); - } - } + private void onClickToggle(View view) { + saveListMembership(item.id, listToggle.isChecked()); + } - private void onClickToggle(View view) { - saveListMembership(item.id, listToggle.isChecked()); - } - - @Override - public void onClick() { - Bundle args=new Bundle(); - args.putString("account", accountId); - args.putString("listID", item.id); - args.putString("listTitle", item.title); - args.putInt("repliesPolicy", item.repliesPolicy.ordinal()); - Nav.goForResult(getActivity(), ListTimelineFragment.class, args, LIST_CHANGED_RESULT, ListTimelinesFragment.this); - } - } + @Override + public void onClick() { + Bundle args=new Bundle(); + args.putString("account", accountId); + args.putString("listID", item.id); + args.putString("listTitle", item.title); + if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal()); + Nav.goForResult(getActivity(), ListTimelineFragment.class, args, LIST_CHANGED_RESULT, ListTimelinesFragment.this); + } + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java index df0d54263..9344756da 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java @@ -1,5 +1,7 @@ package org.joinmastodon.android.model; +import androidx.annotation.NonNull; + import com.google.gson.annotations.SerializedName; import org.joinmastodon.android.api.RequiredField; @@ -11,9 +13,9 @@ public class ListTimeline extends BaseModel { public String id; @RequiredField public String title; - @RequiredField public RepliesPolicy repliesPolicy; + @NonNull @Override public String toString() { return "List{" + diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java index d1f2fbf06..e4438199e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java @@ -10,6 +10,9 @@ import android.widget.Button; import android.widget.LinearLayout; import android.widget.PopupMenu; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.joinmastodon.android.R; import org.joinmastodon.android.model.ListTimeline; @@ -37,9 +40,9 @@ public class ListTimelineEditor extends LinearLayout { setRepliesPolicy(ListTimeline.RepliesPolicy.LIST); } - public void applyList(String title, ListTimeline.RepliesPolicy policy) { + public void applyList(String title, @Nullable ListTimeline.RepliesPolicy policy) { input.getEditText().setText(title); - setRepliesPolicy(policy); + if (policy != null) setRepliesPolicy(policy); } public String getTitle() { @@ -50,7 +53,7 @@ public class ListTimelineEditor extends LinearLayout { return policy; } - public void setRepliesPolicy(ListTimeline.RepliesPolicy policy) { + public void setRepliesPolicy(@NonNull ListTimeline.RepliesPolicy policy) { this.policy = policy; switch (policy) { case FOLLOWED -> button.setText(R.string.sk_list_replies_policy_followed);