update hashtags/lists in home

closes sk22#312
This commit is contained in:
sk 2023-01-23 13:57:17 +01:00
parent e3520df57e
commit c4e23b0fe6
7 changed files with 150 additions and 50 deletions

View File

@ -0,0 +1,11 @@
package org.joinmastodon.android.events;
public class HashtagUpdatedEvent {
public final String name;
public final boolean following;
public HashtagUpdatedEvent(String name, boolean following) {
this.name = name;
this.following = following;
}
}

View File

@ -0,0 +1,9 @@
package org.joinmastodon.android.events;
public class ListDeletedEvent {
public final String id;
public ListDeletedEvent(String id) {
this.id = id;
}
}

View File

@ -0,0 +1,15 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ListTimeline;
public class ListUpdatedCreatedEvent {
public final String id;
public final String title;
public final ListTimeline.RepliesPolicy repliesPolicy;
public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
this.id = id;
this.title = title;
this.repliesPolicy = repliesPolicy;
}
}

View File

@ -11,10 +11,12 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.tags.GetHashtag; import org.joinmastodon.android.api.requests.tags.GetHashtag;
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed; import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline; import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
@ -55,6 +57,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
this.following = newFollowing; this.following = newFollowing;
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag)); followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular); followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
E.post(new HashtagUpdatedEvent(hashtag, following));
} }
@Override @Override

View File

@ -39,6 +39,9 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements; import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
import org.joinmastodon.android.api.requests.lists.GetLists; import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags; import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent; import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.model.Announcement; import org.joinmastodon.android.model.Announcement;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
@ -52,6 +55,9 @@ import org.joinmastodon.android.updater.GithubSelfUpdater;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@ -93,6 +99,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
E.register(this);
accountID = getArguments().getString("account"); accountID = getArguments().getString("account");
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES); timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
assert timelineDefinitions != null; assert timelineDefinitions != null;
@ -212,7 +219,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} }
if(GithubSelfUpdater.needSelfUpdating()){ if(GithubSelfUpdater.needSelfUpdating()){
E.register(this);
updateUpdateState(GithubSelfUpdater.getInstance().getState()); updateUpdateState(GithubSelfUpdater.getInstance().getState());
} }
} }
@ -572,6 +578,50 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
outState.putInt("selectedTab", pager.getCurrentItem()); outState.putInt("selectedTab", pager.getCurrentItem());
} }
@Subscribe
public void onHashtagUpdatedEvent(HashtagUpdatedEvent event) {
handleListEvent(hashtagsItems, h -> h.name.equalsIgnoreCase(event.name), event.following, () -> {
Hashtag hashtag = new Hashtag();
hashtag.name = event.name;
hashtag.following = true;
return hashtag;
});
}
@Subscribe
public void onListDeletedEvent(ListDeletedEvent event) {
handleListEvent(listItems, l -> l.id.equals(event.id), false, null);
}
@Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
handleListEvent(listItems, l -> l.id.equals(event.id), true, () -> {
ListTimeline list = new ListTimeline();
list.id = event.id;
list.title = event.title;
list.repliesPolicy = event.repliesPolicy;
return list;
});
}
private <T> void handleListEvent(
Map<Integer, T> existingThings,
Predicate<T> matchExisting,
boolean shouldBeInList,
Supplier<T> makeNewThing
) {
Optional<Map.Entry<Integer, T>> existingThing = existingThings.entrySet().stream()
.filter(e -> matchExisting.test(e.getValue())).findFirst();
if (shouldBeInList) {
existingThings.put(existingThing.isPresent()
? existingThing.get().getKey() : View.generateViewId(), makeNewThing.get());
createOptionsMenu();
} else if (existingThing.isPresent() && !shouldBeInList) {
existingThings.remove(existingThing.get().getKey());
createOptionsMenu();
}
}
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> { private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
@NonNull @NonNull
@Override @Override

View File

@ -11,10 +11,13 @@ import android.widget.ImageButton;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetList; import org.joinmastodon.android.api.requests.lists.GetList;
import org.joinmastodon.android.api.requests.lists.UpdateList; import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.requests.timelines.GetListTimeline; import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
@ -87,33 +90,30 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.setTitle(R.string.sk_edit_list_title) .setTitle(R.string.sk_edit_list_title)
.setIcon(R.drawable.ic_fluent_people_list_28_regular) .setIcon(R.drawable.ic_fluent_people_list_28_regular)
.setView(editor) .setView(editor)
.setPositiveButton(R.string.save, (d, which) -> .setPositiveButton(R.string.save, (d, which) -> {
new UpdateList(listID, editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() { String newTitle = editor.getTitle().trim();
@Override setTitle(newTitle);
public void onSuccess(ListTimeline list) { new UpdateList(listID, newTitle, editor.getRepliesPolicy()).setCallback(new Callback<>() {
setTitle(list.title); @Override
listTitle = list.title; public void onSuccess(ListTimeline list) {
repliesPolicy = list.repliesPolicy; setTitle(list.title);
Bundle result = new Bundle(); listTitle = list.title;
result.putString("listID", listID); repliesPolicy = list.repliesPolicy;
result.putString("listTitle", listTitle); E.post(new ListUpdatedCreatedEvent(listID, listTitle, repliesPolicy));
if (repliesPolicy != null) result.putInt("repliesPolicy", repliesPolicy.ordinal()); }
setResult(true, result);
}
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse error) {
error.showToast(getContext()); setTitle(listTitle);
} error.showToast(getContext());
}).exec(accountID)) }
}).exec(accountID);
})
.setNegativeButton(R.string.cancel, (d, which) -> {}) .setNegativeButton(R.string.cancel, (d, which) -> {})
.show(); .show();
} else if (item.getItemId() == R.id.delete) { } else if (item.getItemId() == R.id.delete) {
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> { UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
Bundle result = new Bundle(); E.post(new ListDeletedEvent(listID));
result.putBoolean("deleted", true);
result.putString("listID", listID);
setResult(true, result);
Nav.finish(this); Nav.finish(this);
}); });
} }

View File

@ -12,12 +12,17 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList; import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.CreateList; import org.joinmastodon.android.api.requests.lists.CreateList;
import org.joinmastodon.android.api.requests.lists.GetLists; import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList; import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
@ -37,8 +42,6 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop { public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
private static final int LIST_CHANGED_RESULT = 987;
private String accountId; private String accountId;
private String profileAccountId; private String profileAccountId;
private final HashMap<String, Boolean> userInListBefore = new HashMap<>(); private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
@ -55,6 +58,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
Bundle args=getArguments(); Bundle args=getArguments();
accountId=args.getString("account"); accountId=args.getString("account");
setHasOptionsMenu(true); setHasOptionsMenu(true);
E.register(this);
if(args.containsKey("profileAccount")){ if(args.containsKey("profileAccount")){
profileAccountId=args.getString("profileAccount"); profileAccountId=args.getString("profileAccount");
@ -98,6 +102,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
saveListMembership(list.id, true); saveListMembership(list.id, true);
data.add(0, list); data.add(0, list);
adapter.notifyItemRangeInserted(0, 1); adapter.notifyItemRangeInserted(0, 1);
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
} }
@Override @Override
@ -116,9 +121,14 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
userInList.put(listId, isMember); userInList.put(listId, isMember);
List<String> accountIdList = Collections.singletonList(profileAccountId); List<String> accountIdList = Collections.singletonList(profileAccountId);
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList); MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
req.setCallback(new SimpleCallback<>(this) { req.setCallback(new Callback<>() {
@Override @Override
public void onSuccess(Object o) {} public void onSuccess(Object o) {}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountId); }).exec(accountId);
} }
@ -154,29 +164,31 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
.exec(accountId); .exec(accountId);
} }
@Override @Subscribe
public void onFragmentResult(int reqCode, boolean listChanged, Bundle result){ public void onListDeletedEvent(ListDeletedEvent event) {
if (reqCode == LIST_CHANGED_RESULT && listChanged) { for (int i = 0; i < data.size(); i++) {
String listID = result.getString("listID"); ListTimeline item = data.get(i);
for (int i = 0; i < data.size(); i++) { if (item.id.equals(event.id)) {
ListTimeline item = data.get(i); data.remove(i);
if (item.id.equals(listID)) { adapter.notifyItemRemoved(i);
if (result.getBoolean("deleted")) { break;
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;
}
}
}
} }
@Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i);
if (item.id.equals(event.id)) {
item.title = event.title;
item.repliesPolicy = event.repliesPolicy;
adapter.notifyItemChanged(i);
break;
}
}
}
@Override @Override
protected RecyclerView.Adapter<ListViewHolder> getAdapter() { protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
return adapter = new ListsAdapter(); return adapter = new ListsAdapter();
@ -240,7 +252,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
args.putString("listID", item.id); args.putString("listID", item.id);
args.putString("listTitle", item.title); args.putString("listTitle", item.title);
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal()); if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
Nav.goForResult(getActivity(), ListTimelineFragment.class, args, LIST_CHANGED_RESULT, ListTimelinesFragment.this); Nav.go(getActivity(), ListTimelineFragment.class, args);
} }
} }
} }