From 5ca288a04bbcf47014b0c29fb774b619fabf353b Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 8 Jul 2023 16:17:13 +0200 Subject: [PATCH] Add activity for timelines --- app/src/main/AndroidManifest.xml | 4 + .../mastodon/activities/TimelineActivity.java | 77 +++++++++++++++++++ .../endpoints/MastodonTimelinesService.java | 10 +++ .../android/mastodon/helper/Helper.java | 2 + .../mastodon/helper/PinnedTimelineHelper.java | 7 +- .../mastodon/ui/drawer/StatusAdapter.java | 19 ++++- .../timeline/FragmentMastodonTimeline.java | 32 +++++++- .../viewmodel/mastodon/TimelinesVM.java | 55 +++++++++++++ .../drawable/baseline_exit_to_app_24.xml | 0 .../mastodon/drawable/baseline_live_tv_24.xml | 10 +++ .../drawable/baseline_ondemand_video_24.xml | 0 .../drawable/baseline_show_chart_24.xml | 0 .../mastodon/drawable/lemmy.xml | 0 .../mastodon/drawable/lemmy_logo.xml | 0 .../mastodon/layout/activity_timeline.xml | 20 +++++ 15 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/mastodon/activities/TimelineActivity.java rename app/src/main/res/{layouts => drawables}/mastodon/drawable/baseline_exit_to_app_24.xml (100%) create mode 100644 app/src/main/res/drawables/mastodon/drawable/baseline_live_tv_24.xml rename app/src/main/res/{layouts => drawables}/mastodon/drawable/baseline_ondemand_video_24.xml (100%) rename app/src/main/res/{layouts => drawables}/mastodon/drawable/baseline_show_chart_24.xml (100%) rename app/src/main/res/{layouts => drawables}/mastodon/drawable/lemmy.xml (100%) rename app/src/main/res/{layouts => drawables}/mastodon/drawable/lemmy_logo.xml (100%) create mode 100644 app/src/main/res/layouts/mastodon/layout/activity_timeline.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1b8f4706a..9bf79691a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -265,6 +265,10 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/action_about" android:theme="@style/AppThemeBar" /> + . */ + +import android.os.Bundle; +import android.view.MenuItem; + +import org.jetbrains.annotations.NotNull; + +import app.fedilab.android.R; +import app.fedilab.android.databinding.ActivityTimelineBinding; +import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; +import app.fedilab.android.mastodon.client.entities.app.Timeline; +import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTimeline; + + +public class TimelineActivity extends BaseBarActivity { + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + app.fedilab.android.databinding.ActivityTimelineBinding binding = ActivityTimelineBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + Bundle b = getIntent().getExtras(); + Timeline.TimeLineEnum timelineType = null; + String lemmy_post_id = null; + PinnedTimeline pinnedTimeline = null; + if (b != null) { + timelineType = (Timeline.TimeLineEnum) b.get(Helper.ARG_TIMELINE_TYPE); + lemmy_post_id = b.getString(Helper.ARG_LEMMY_POST_ID, null); + pinnedTimeline = (PinnedTimeline) b.getSerializable(Helper.ARG_REMOTE_INSTANCE); + } + if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { + setTitle(pinnedTimeline.remoteInstance.displayName); + } + FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, timelineType); + bundle.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline); + bundle.putSerializable(Helper.ARG_LEMMY_POST_ID, lemmy_post_id); + fragmentMastodonTimeline.setArguments(bundle); + + getSupportFragmentManager().beginTransaction() + .add(R.id.fragment_container_view, fragmentMastodonTimeline).commit(); + } + + + @Override + public boolean onOptionsItemSelected(@NotNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonTimelinesService.java b/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonTimelinesService.java index 3a014da05..f786199bd 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonTimelinesService.java +++ b/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonTimelinesService.java @@ -22,6 +22,7 @@ import app.fedilab.android.mastodon.client.entities.api.Marker; import app.fedilab.android.mastodon.client.entities.api.MastodonList; import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Tag; +import app.fedilab.android.mastodon.client.entities.lemmy.LemmyPost; import app.fedilab.android.mastodon.client.entities.misskey.MisskeyNote; import app.fedilab.android.mastodon.client.entities.nitter.Nitter; import app.fedilab.android.mastodon.client.entities.peertube.PeertubeVideo; @@ -230,6 +231,15 @@ public interface MastodonTimelinesService { Call> getMisskey(@Body MisskeyNote.MisskeyParams params); + @GET("api/v3/post/list") + Call> getLemmyMain(@Query("limit") Integer limit, + @Query("page") String page); + + @GET("api/v3/comment/list") + Call> getLemmyThread(@Query("post_id") String post_id, + @Query("limit") Integer limit, + @Query("page") String page); + //Public timelines for Misskey @FormUrlEncoded @POST("api/notes") diff --git a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java index 368c493de..6d331a37b 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java @@ -259,6 +259,8 @@ public class Helper { public static final String ARG_STATUS_ID = "ARG_STATUS_ID"; public static final String ARG_WORK_ID = "ARG_WORK_ID"; public static final String ARG_LIST_ID = "ARG_LIST_ID"; + public static final String ARG_LEMMY_POST_ID = "ARG_LEMMY_POST_ID"; + public static final String ARG_SEARCH_KEYWORD = "ARG_SEARCH_KEYWORD"; public static final String ARG_DIRECTORY_ORDER = "ARG_DIRECTORY_ORDER"; public static final String ARG_DIRECTORY_LOCAL = "ARG_DIRECTORY_LOCAL"; diff --git a/app/src/main/java/app/fedilab/android/mastodon/helper/PinnedTimelineHelper.java b/app/src/main/java/app/fedilab/android/mastodon/helper/PinnedTimelineHelper.java index 1a44a19e7..998ec2d01 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/helper/PinnedTimelineHelper.java +++ b/app/src/main/java/app/fedilab/android/mastodon/helper/PinnedTimelineHelper.java @@ -370,7 +370,9 @@ public class PinnedTimelineHelper { case MASTODON: tabCustomViewBinding.icon.setImageResource(R.drawable.mastodon_icon_item); break; - + case LEMMY: + tabCustomViewBinding.icon.setImageResource(R.drawable.lemmy); + break; case MISSKEY: tabCustomViewBinding.icon.setImageResource(R.drawable.misskey); break; @@ -461,6 +463,9 @@ public class PinnedTimelineHelper { case MISSKEY: item.setIcon(R.drawable.misskey); break; + case LEMMY: + item.setIcon(R.drawable.lemmy); + break; case PIXELFED: item.setIcon(R.drawable.pixelfed); break; diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java index bebbeba45..187ca79cd 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java @@ -139,6 +139,7 @@ import app.fedilab.android.mastodon.activities.ProfileActivity; import app.fedilab.android.mastodon.activities.ReportActivity; import app.fedilab.android.mastodon.activities.StatusHistoryActivity; import app.fedilab.android.mastodon.activities.StatusInfoActivity; +import app.fedilab.android.mastodon.activities.TimelineActivity; import app.fedilab.android.mastodon.activities.admin.AdminAccountActivity; import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Poll; @@ -146,6 +147,8 @@ import app.fedilab.android.mastodon.client.entities.api.Reaction; import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.app.Account; import app.fedilab.android.mastodon.client.entities.app.BaseAccount; +import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; +import app.fedilab.android.mastodon.client.entities.app.RemoteInstance; import app.fedilab.android.mastodon.client.entities.app.StatusCache; import app.fedilab.android.mastodon.client.entities.app.StatusDraft; import app.fedilab.android.mastodon.client.entities.app.Timeline; @@ -187,6 +190,9 @@ public class StatusAdapter extends RecyclerView.Adapter private final List statusList; private final boolean minified; private final Timeline.TimeLineEnum timelineType; + public RemoteInstance.InstanceType type; + public String lemmy_post_id; + public PinnedTimeline pinnedTimeline; private final boolean canBeFederated; private final boolean checkRemotely; public FetchMoreCallBack fetchMoreCallBack; @@ -1989,7 +1995,18 @@ public class StatusAdapter extends RecyclerView.Adapter ((ContextActivity) context).setCurrentFragment((FragmentMastodonContext) fragment); } else { if (remote) { - if (!(context instanceof ContextActivity)) { //We are not already checking a remote conversation + //Lemmy main post that should open Lemmy threads + if (adapter instanceof StatusAdapter && ((StatusAdapter) adapter).type == RemoteInstance.InstanceType.LEMMY && ((StatusAdapter) adapter).lemmy_post_id == null) { + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.ARG_REMOTE_INSTANCE, ((StatusAdapter) adapter).pinnedTimeline); + bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.REMOTE); + bundle.putSerializable(Helper.ARG_LEMMY_POST_ID, ((StatusAdapter) adapter).lemmy_post_id); + Intent intent = new Intent(context, TimelineActivity.class); + intent.putExtras(bundle); + context.startActivity(intent); + + } //Classic other cases for remote instances that will search the remote context + else if (!(context instanceof ContextActivity)) { //We are not already checking a remote conversation Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show(); searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1) .observe((LifecycleOwner) context, results -> { diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java index 07e87bfda..a5f86e034 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -89,6 +89,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private List timelineStatuses; private boolean retry_for_home_done; + private String lemmy_post_id; //Handle actions that can be done in other fragments private final BroadcastReceiver receive_action = new BroadcastReceiver() { @@ -382,6 +383,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. retry_for_home_done = false; if (getArguments() != null) { timelineType = (Timeline.TimeLineEnum) getArguments().get(Helper.ARG_TIMELINE_TYPE); + lemmy_post_id = getArguments().getString(Helper.ARG_LEMMY_POST_ID, null); list_id = getArguments().getString(Helper.ARG_LIST_ID, null); search = getArguments().getString(Helper.ARG_SEARCH_KEYWORD, null); searchCache = getArguments().getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null); @@ -625,6 +627,14 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } statusAdapter = new StatusAdapter(timelineStatuses, timelineType, minified, canBeFederated, checkRemotely); statusAdapter.fetchMoreCallBack = this; + statusAdapter.pinnedTimeline = pinnedTimeline; + //------Specifications for Lemmy timelines + if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { + statusAdapter.type = pinnedTimeline.remoteInstance.type; + } + statusAdapter.lemmy_post_id = lemmy_post_id; + //--------------- + if (statusReport != null) { scrollToTop(); } @@ -1019,7 +1029,27 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } }); } - } //PEERTUBE TIMELINES + } //LEMMY TIMELINES + else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.LEMMY) { + if (direction == null) { + timelinesVM.getLemmy(remoteInstance, lemmy_post_id, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); + } else if (direction == DIRECTION.BOTTOM) { + timelinesVM.getLemmy(remoteInstance, lemmy_post_id, max_id, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false, true, fetchStatus)); + } else if (direction == DIRECTION.TOP) { + flagLoading = false; + } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { + timelinesVM.getLemmy(remoteInstance, lemmy_post_id, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesRefresh -> { + if (statusAdapter != null) { + dealWithPagination(statusesRefresh, direction, true, true, fetchStatus); + } else { + initializeStatusesCommonView(statusesRefresh); + } + }); + } + }//PEERTUBE TIMELINES else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.PEERTUBE) { if (direction == null) { timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity())) diff --git a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java index 55800510c..64abd9725 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java +++ b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java @@ -52,6 +52,7 @@ import app.fedilab.android.mastodon.client.entities.app.BaseAccount; import app.fedilab.android.mastodon.client.entities.app.StatusCache; import app.fedilab.android.mastodon.client.entities.app.StatusDraft; import app.fedilab.android.mastodon.client.entities.app.Timeline; +import app.fedilab.android.mastodon.client.entities.lemmy.LemmyPost; import app.fedilab.android.mastodon.client.entities.misskey.MisskeyNote; import app.fedilab.android.mastodon.client.entities.nitter.Nitter; import app.fedilab.android.mastodon.client.entities.peertube.PeertubeVideo; @@ -335,6 +336,60 @@ public class TimelinesVM extends AndroidViewModel { return statusesMutableLiveData; } + /** + * Public timeline for Lemmy + * + * @param post_id Return comments for post_id, if null it's for main threads + * @param page Return results from this page + * @param limit Maximum number of results to return. Defaults to 20. + * @return {@link LiveData} containing a {@link Statuses} + */ + public LiveData getLemmy(@NonNull String instance, String post_id, + String page, + Integer limit) { + MastodonTimelinesService mastodonTimelinesService = initInstanceOnly(instance); + statusesMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + + Call> publicTlCall; + if (post_id == null) { + publicTlCall = mastodonTimelinesService.getLemmyMain(limit, page); + } else { + publicTlCall = mastodonTimelinesService.getLemmyThread(post_id, limit, page); + } + Statuses statuses = new Statuses(); + if (publicTlCall != null) { + try { + Response> publicTlResponse = publicTlCall.execute(); + if (publicTlResponse.isSuccessful()) { + List lemmyPostList = publicTlResponse.body(); + List statusList = new ArrayList<>(); + if (lemmyPostList != null) { + for (LemmyPost lemmyPost : lemmyPostList) { + Status status = LemmyPost.convert(lemmyPost, instance); + statusList.add(status); + } + } + statuses.statuses = TimelineHelper.filterStatus(getApplication(), statusList, Timeline.TimeLineEnum.PUBLIC); + statuses.pagination = new Pagination(); + if (statusList.size() > 0) { + statuses.pagination.min_id = statusList.get(0).id; + statuses.pagination.max_id = statusList.get(statusList.size() - 1).id; + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); + mainHandler.post(myRunnable); + }).start(); + return statusesMutableLiveData; + } + + /** * Public timeline for Peertube * diff --git a/app/src/main/res/layouts/mastodon/drawable/baseline_exit_to_app_24.xml b/app/src/main/res/drawables/mastodon/drawable/baseline_exit_to_app_24.xml similarity index 100% rename from app/src/main/res/layouts/mastodon/drawable/baseline_exit_to_app_24.xml rename to app/src/main/res/drawables/mastodon/drawable/baseline_exit_to_app_24.xml diff --git a/app/src/main/res/drawables/mastodon/drawable/baseline_live_tv_24.xml b/app/src/main/res/drawables/mastodon/drawable/baseline_live_tv_24.xml new file mode 100644 index 000000000..fc5ca9b37 --- /dev/null +++ b/app/src/main/res/drawables/mastodon/drawable/baseline_live_tv_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layouts/mastodon/drawable/baseline_ondemand_video_24.xml b/app/src/main/res/drawables/mastodon/drawable/baseline_ondemand_video_24.xml similarity index 100% rename from app/src/main/res/layouts/mastodon/drawable/baseline_ondemand_video_24.xml rename to app/src/main/res/drawables/mastodon/drawable/baseline_ondemand_video_24.xml diff --git a/app/src/main/res/layouts/mastodon/drawable/baseline_show_chart_24.xml b/app/src/main/res/drawables/mastodon/drawable/baseline_show_chart_24.xml similarity index 100% rename from app/src/main/res/layouts/mastodon/drawable/baseline_show_chart_24.xml rename to app/src/main/res/drawables/mastodon/drawable/baseline_show_chart_24.xml diff --git a/app/src/main/res/layouts/mastodon/drawable/lemmy.xml b/app/src/main/res/drawables/mastodon/drawable/lemmy.xml similarity index 100% rename from app/src/main/res/layouts/mastodon/drawable/lemmy.xml rename to app/src/main/res/drawables/mastodon/drawable/lemmy.xml diff --git a/app/src/main/res/layouts/mastodon/drawable/lemmy_logo.xml b/app/src/main/res/drawables/mastodon/drawable/lemmy_logo.xml similarity index 100% rename from app/src/main/res/layouts/mastodon/drawable/lemmy_logo.xml rename to app/src/main/res/drawables/mastodon/drawable/lemmy_logo.xml diff --git a/app/src/main/res/layouts/mastodon/layout/activity_timeline.xml b/app/src/main/res/layouts/mastodon/layout/activity_timeline.xml new file mode 100644 index 000000000..a76712245 --- /dev/null +++ b/app/src/main/res/layouts/mastodon/layout/activity_timeline.xml @@ -0,0 +1,20 @@ + + \ No newline at end of file