From 21a526dda99a4ebd305d51d40c1d0e29e3824c7b Mon Sep 17 00:00:00 2001 From: obstsalatschuessel Date: Thu, 3 Nov 2022 08:18:48 +0100 Subject: [PATCH 1/3] Add list timelines view * get accounts list timelines from API * display available lists in discover view tab * display list timeline --- .../android/api/requests/lists/GetLists.java | 14 +++ .../requests/timelines/GetListTimeline.java | 22 +++++ .../fragments/ListTimelineFragment.java | 75 +++++++++++++++ .../fragments/ListTimelinesFragment.java | 96 +++++++++++++++++++ .../fragments/discover/DiscoverFragment.java | 11 ++- .../android/model/ListTimeline.java | 34 +++++++ .../android/ui/utils/UiUtils.java | 10 ++ .../main/res/layout/item_list_timeline.xml | 19 ++++ mastodon/src/main/res/values/ids.xml | 1 + mastodon/src/main/res/values/strings.xml | 1 + 10 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/GetLists.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java create mode 100644 mastodon/src/main/res/layout/item_list_timeline.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/GetLists.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/GetLists.java new file mode 100644 index 000000000..a4f1752e7 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/GetLists.java @@ -0,0 +1,14 @@ +package org.joinmastodon.android.api.requests.lists; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.ListTimeline; + +import java.util.List; + +public class GetLists extends MastodonAPIRequest>{ + public GetLists() { + super(HttpMethod.GET, "/lists", new TypeToken<>(){}); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java new file mode 100644 index 000000000..145a740bc --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java @@ -0,0 +1,22 @@ +package org.joinmastodon.android.api.requests.timelines; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Status; + +import java.util.List; + +public class GetListTimeline extends MastodonAPIRequest> { + public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID) { + super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){}); + if(maxID!=null) + addQueryParameter("max_id", maxID); + if(minID!=null) + addQueryParameter("min_id", minID); + if(limit>0) + addQueryParameter("limit", ""+limit); + if(sinceID!=null) + addQueryParameter("since_id", sinceID); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java new file mode 100644 index 000000000..e9aa51875 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java @@ -0,0 +1,75 @@ +package org.joinmastodon.android.fragments; + +import android.app.Activity; +import android.media.MediaRouter; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.timelines.GetListTimeline; +import org.joinmastodon.android.model.Status; + +import java.util.List; + +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.utils.V; + + +public class ListTimelineFragment extends StatusListFragment { + private String listID; + private String listTitle; + private ImageButton fab; + + public ListTimelineFragment() { + setListLayoutId(R.layout.recycler_fragment_with_fab); + } + + @Override + public void onAttach(Activity activity){ + super.onAttach(activity); + listID=getArguments().getString("listID"); + listTitle=getArguments().getString("listTitle"); + setTitle(listTitle); + } + + @Override + protected void doLoadData(int offset, int count) { + currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null) + .setCallback(new SimpleCallback<>(this) { + @Override + public void onSuccess(List result) { + onDataLoaded(result, !result.isEmpty()); + } + }) + .exec(accountID); + } + + @Override + protected void onShown() { + super.onShown(); + if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) + loadData(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + fab=view.findViewById(R.id.fab); + fab.setOnClickListener(this::onFabClick); + } + + private void onFabClick(View v){ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putString("prefilledText", listID+' '); + Nav.go(getActivity(), ComposeFragment.class, args); + } + + @Override + protected void onSetFabBottomInset(int inset) { + ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset; + } +} \ No newline at end of file diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java new file mode 100644 index 000000000..70da67991 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java @@ -0,0 +1,96 @@ +package org.joinmastodon.android.fragments; + +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.RecyclerView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.lists.GetLists; +import org.joinmastodon.android.fragments.ScrollableToTop; +import org.joinmastodon.android.model.ListTimeline; +import org.joinmastodon.android.ui.utils.UiUtils; + +import java.util.List; + +import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.fragments.BaseRecyclerFragment; +import me.grishka.appkit.utils.BindableViewHolder; +import me.grishka.appkit.views.UsableRecyclerView; + +public class ListTimelinesFragment extends BaseRecyclerFragment implements ScrollableToTop { + private String accountId; + + public ListTimelinesFragment() { + super(10); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + accountId=getArguments().getString("account"); + } + + @Override + protected void doLoadData(int offset, int count){ + currentRequest=new GetLists() + .setCallback(new SimpleCallback<>(this) { + @Override + public void onSuccess(List result) { + onDataLoaded(result, false); + } + }) + .exec(accountId); + } + + @Override + protected RecyclerView.Adapter getAdapter() { + return new ListsAdapter(); + } + + @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(); + } + + @Override + public void onBindViewHolder(@NonNull ListViewHolder holder, int position) { + holder.bind(data.get(position)); + } + + @Override + public int getItemCount() { + return data.size(); + } + } + + private class ListViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ + private final TextView title; + + public ListViewHolder(){ + super(getActivity(), R.layout.item_list_timeline, list); + title=findViewById(R.id.title); + } + + @Override + public void onBind(ListTimeline item) { + title.setText(item.title); + } + + @Override + public void onClick() { + UiUtils.openListTimeline(getActivity(), accountId, item); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java index a559c931d..b12868260 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java @@ -19,6 +19,7 @@ import android.widget.TextView; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.ScrollableToTop; +import org.joinmastodon.android.fragments.ListTimelinesFragment; import org.joinmastodon.android.ui.SimpleViewHolder; import org.joinmastodon.android.ui.tabs.TabLayout; import org.joinmastodon.android.ui.tabs.TabLayoutMediator; @@ -51,6 +52,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, private DiscoverAccountsFragment accountsFragment; private SearchFragment searchFragment; private LocalTimelineFragment localTimelineFragment; + private ListTimelinesFragment listTimelinesFragment; private String accountID; private Runnable searchDebouncer=this::onSearchChangedDebounced; @@ -72,7 +74,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, tabLayout=view.findViewById(R.id.tabbar); pager=view.findViewById(R.id.pager); - tabViews=new FrameLayout[5]; + tabViews=new FrameLayout[6]; for(int i=0;i R.id.discover_news; case 3 -> R.id.discover_local_timeline; case 4 -> R.id.discover_users; + case 5 -> R.id.discover_lists; default -> throw new IllegalStateException("Unexpected value: "+i); }); tabView.setVisibility(View.GONE); @@ -126,12 +129,16 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, localTimelineFragment=new LocalTimelineFragment(); localTimelineFragment.setArguments(args); + listTimelinesFragment=new ListTimelinesFragment(); + listTimelinesFragment.setArguments(args); + getChildFragmentManager().beginTransaction() .add(R.id.discover_posts, postsFragment) .add(R.id.discover_local_timeline, localTimelineFragment) .add(R.id.discover_hashtags, hashtagsFragment) .add(R.id.discover_news, newsFragment) .add(R.id.discover_users, accountsFragment) + .add(R.id.discover_lists, listTimelinesFragment) .commit(); } @@ -144,6 +151,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, case 2 -> R.string.news; case 3 -> R.string.local_timeline; case 4 -> R.string.for_you; + case 5 -> R.string.list_timelines; default -> throw new IllegalStateException("Unexpected value: "+position); }); tab.view.textView.setAllCaps(true); @@ -271,6 +279,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, case 2 -> newsFragment; case 3 -> localTimelineFragment; case 4 -> accountsFragment; + case 5 -> listTimelinesFragment; default -> throw new IllegalStateException("Unexpected value: "+page); }; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java new file mode 100644 index 000000000..df0d54263 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/ListTimeline.java @@ -0,0 +1,34 @@ +package org.joinmastodon.android.model; + +import com.google.gson.annotations.SerializedName; + +import org.joinmastodon.android.api.RequiredField; +import org.parceler.Parcel; + +@Parcel +public class ListTimeline extends BaseModel { + @RequiredField + public String id; + @RequiredField + public String title; + @RequiredField + public RepliesPolicy repliesPolicy; + + @Override + public String toString() { + return "List{" + + "id='" + id + '\'' + + ", title='" + title + '\'' + + ", repliesPolicy=" + repliesPolicy + + '}'; + } + + public enum RepliesPolicy{ + @SerializedName("followed") + FOLLOWED, + @SerializedName("list") + LIST, + @SerializedName("none") + NONE + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index f96469375..2d77b18fa 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -45,10 +45,12 @@ import org.joinmastodon.android.api.requests.statuses.GetStatusByID; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.StatusDeletedEvent; import org.joinmastodon.android.fragments.HashtagTimelineFragment; +import org.joinmastodon.android.fragments.ListTimelineFragment; import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Emoji; +import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.M3AlertDialogBuilder; @@ -294,6 +296,14 @@ public class UiUtils{ Nav.go((Activity)context, HashtagTimelineFragment.class, args); } + public static void openListTimeline(Context context, String accountID, ListTimeline list){ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putString("listID", list.id); + args.putString("listTitle", list.title); + Nav.go((Activity)context, ListTimelineFragment.class, args); + } + public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){ showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), onConfirmed); } diff --git a/mastodon/src/main/res/layout/item_list_timeline.xml b/mastodon/src/main/res/layout/item_list_timeline.xml new file mode 100644 index 000000000..948f09d6d --- /dev/null +++ b/mastodon/src/main/res/layout/item_list_timeline.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/values/ids.xml b/mastodon/src/main/res/values/ids.xml index 6545f9cad..78b82110a 100644 --- a/mastodon/src/main/res/values/ids.xml +++ b/mastodon/src/main/res/values/ids.xml @@ -12,6 +12,7 @@ + diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index 2ee4d9975..cf4fe5156 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -387,4 +387,5 @@ Mastodon and your privacy Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy. I Agree + Lists \ No newline at end of file From f4b1bde8c5291e8a3e1179cf84241cd3aa7063aa Mon Sep 17 00:00:00 2001 From: sk Date: Fri, 11 Nov 2022 02:40:16 +0100 Subject: [PATCH 2/3] improve list item styling --- .../fragments/ListTimelineFragment.java | 2 +- .../main/res/layout/item_list_timeline.xml | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) 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 e9aa51875..465170bb9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java @@ -72,4 +72,4 @@ public class ListTimelineFragment extends StatusListFragment { protected void onSetFabBottomInset(int inset) { ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset; } -} \ No newline at end of file +} diff --git a/mastodon/src/main/res/layout/item_list_timeline.xml b/mastodon/src/main/res/layout/item_list_timeline.xml index 948f09d6d..ecaf7eb17 100644 --- a/mastodon/src/main/res/layout/item_list_timeline.xml +++ b/mastodon/src/main/res/layout/item_list_timeline.xml @@ -1,19 +1,28 @@ - + android:layout_height="56dp" + android:paddingHorizontal="16dp" + android:orientation="horizontal" + android:gravity="center"> + + - \ No newline at end of file + From 679ede1124842be8ede1735680a20840fa7f859a Mon Sep 17 00:00:00 2001 From: sk Date: Fri, 11 Nov 2022 03:02:17 +0100 Subject: [PATCH 3/3] update strings --- mastodon/src/main/res/values-de-rDE/strings.xml | 1 + mastodon/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mastodon/src/main/res/values-de-rDE/strings.xml b/mastodon/src/main/res/values-de-rDE/strings.xml index 0c19347fb..b460773be 100644 --- a/mastodon/src/main/res/values-de-rDE/strings.xml +++ b/mastodon/src/main/res/values-de-rDE/strings.xml @@ -377,4 +377,5 @@ Download (%s) Installieren + Listen diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index cf4fe5156..ba049e7b6 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -387,5 +387,5 @@ Mastodon and your privacy Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy. I Agree - Lists + Lists \ No newline at end of file