From d5085c5899a1ef71e492c195529012a86119536c Mon Sep 17 00:00:00 2001 From: FineFindus Date: Sat, 15 Jun 2024 18:49:47 +0200 Subject: [PATCH 1/3] feat(Discover): add Timeline to trending links Adds a timeline of statuses that posted about a trending link. See https://github.com/mastodon/mastodon/pull/30381 for more details. --- .../timelines/GetTrendingLinksTimeline.java | 23 +++ .../discover/DiscoverNewsFragment.java | 7 +- .../DiscoverTrendingLinkTimelineFragment.java | 192 ++++++++++++++++++ .../ic_fluent_document_one_page_24_filled.xml | 9 + .../layout/header_trending_link_timeline.xml | 52 +++++ .../main/res/menu/trending_links_timeline.xml | 8 + mastodon/src/main/res/values/strings_mo.xml | 1 + 7 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetTrendingLinksTimeline.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverTrendingLinkTimelineFragment.java create mode 100644 mastodon/src/main/res/drawable/ic_fluent_document_one_page_24_filled.xml create mode 100644 mastodon/src/main/res/layout/header_trending_link_timeline.xml create mode 100644 mastodon/src/main/res/menu/trending_links_timeline.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetTrendingLinksTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetTrendingLinksTimeline.java new file mode 100644 index 000000000..428fdc3cf --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetTrendingLinksTimeline.java @@ -0,0 +1,23 @@ +package org.joinmastodon.android.api.requests.timelines; + +import androidx.annotation.NonNull; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Status; + +import java.util.List; + +public class GetTrendingLinksTimeline extends MastodonAPIRequest>{ + public GetTrendingLinksTimeline(@NonNull String url, String maxID, String minID, int limit){ + super(HttpMethod.GET, "/timelines/link/", new TypeToken<>(){}); + addQueryParameter("url", url); + if(maxID!=null) + addQueryParameter("max_id", maxID); + if(minID!=null) + addQueryParameter("min_id", minID); + if(limit>0) + addQueryParameter("limit", ""+limit); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java index 74ddb63ac..b6566ba8f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java @@ -21,6 +21,7 @@ import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable; import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.utils.ProvidesAssistContent; +import org.parceler.Parcels; import java.util.ArrayList; import java.util.List; @@ -29,6 +30,7 @@ import java.util.stream.Collectors; import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import me.grishka.appkit.Nav; import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter; @@ -215,7 +217,10 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment im @Override public void onClick(){ - UiUtils.launchWebBrowser(getActivity(), item.url); + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("trendingLink", Parcels.wrap(item)); + Nav.go(getActivity(), DiscoverTrendingLinkTimelineFragment.class, args); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverTrendingLinkTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverTrendingLinkTimelineFragment.java new file mode 100644 index 000000000..bc53b052d --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverTrendingLinkTimelineFragment.java @@ -0,0 +1,192 @@ +package org.joinmastodon.android.fragments.discover; + +import android.app.Activity; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.timelines.GetTrendingLinksTimeline; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.fragments.HomeTabFragment; +import org.joinmastodon.android.fragments.StatusListFragment; +import org.joinmastodon.android.model.Card; +import org.joinmastodon.android.model.FilterContext; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.parceler.Parcels; + +import java.util.List; + +import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.utils.MergeRecyclerAdapter; +import me.grishka.appkit.utils.SingleViewRecyclerAdapter; +import me.grishka.appkit.utils.V; + +//TODO: replace this implementation when upstream implements their own design +public class DiscoverTrendingLinkTimelineFragment extends StatusListFragment{ + private Card trendingLink; + private TextView headerTitle, headerSubtitle; + private Button openLinkButton; + private boolean toolbarContentVisible; + + private Menu optionsMenu; + private MenuInflater optionsMenuInflater; + + @Override + protected boolean wantsComposeButton() { + return true; + } + + @Override + public void onAttach(Activity activity){ + super.onAttach(activity); + trendingLink=Parcels.unwrap(getArguments().getParcelable("trendingLink")); + setTitle(trendingLink.title); + setHasOptionsMenu(true); + } + + + @Override + protected void doLoadData(int offset, int count){ + currentRequest=new GetTrendingLinksTimeline(trendingLink.url, getMaxID(), null, count) + .setCallback(new SimpleCallback<>(this){ + @Override + public void onSuccess(List result){ + if(getActivity()==null) return; + boolean more=applyMaxID(result); + AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); + onDataLoaded(result, more); + } + }) + .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); + + if(getParentFragment() instanceof HomeTabFragment) return; + + list.addOnScrollListener(new RecyclerView.OnScrollListener(){ + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ + View topChild=recyclerView.getChildAt(0); + int firstChildPos=recyclerView.getChildAdapterPosition(topChild); + float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight()); + toolbarTitleView.setAlpha(newAlpha); + boolean newToolbarVisibility=newAlpha>0.5f; + if(newToolbarVisibility!=toolbarContentVisible){ + toolbarContentVisible=newToolbarVisibility; + createOptionsMenu(); + } + } + }); + } + + @Override + protected void onSetFabBottomInset(int inset){ + ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+inset; + } + + @Override + protected FilterContext getFilterContext() { + return FilterContext.PUBLIC; + } + + @Override + public Uri getWebUri(Uri.Builder base) { + //TODO: add URL link once web version implements a UI + return base.path("/explore/links").build(); + } + + @Override + protected RecyclerView.Adapter getAdapter(){ + View header=getActivity().getLayoutInflater().inflate(R.layout.header_trending_link_timeline, list, false); + headerTitle=header.findViewById(R.id.title); + headerSubtitle=header.findViewById(R.id.subtitle); + openLinkButton=header.findViewById(R.id.profile_action_btn); + + headerTitle.setText(trendingLink.title); + openLinkButton.setVisibility(View.GONE); + openLinkButton.setOnClickListener(v->{ + if(trendingLink==null) + return; + openLink(); + }); + updateHeader(); + + MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter(); + if(!(getParentFragment() instanceof HomeTabFragment)){ + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header)); + } + mergeAdapter.addAdapter(super.getAdapter()); + return mergeAdapter; + } + + @Override + protected int getMainAdapterOffset(){ + return 1; + } + + private void createOptionsMenu(){ + optionsMenu.clear(); + optionsMenuInflater.inflate(R.menu.trending_links_timeline, optionsMenu); + MenuItem openLinkMenuItem=optionsMenu.findItem(R.id.open_link); + openLinkMenuItem.setVisible(toolbarContentVisible); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + inflater.inflate(R.menu.trending_links_timeline, menu); + super.onCreateOptionsMenu(menu, inflater); + optionsMenu=menu; + optionsMenuInflater=inflater; + createOptionsMenu(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + if (super.onOptionsItemSelected(item)) return true; + if (item.getItemId() == R.id.open_link && trendingLink!=null) { + openLink(); + } + return true; + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f); + createOptionsMenu(); + } + + private void updateHeader(){ + if(trendingLink==null || getActivity()==null) + return; + //TODO: update to show mastodon account once fully implemented upstream + headerSubtitle.setText(getContext().getString(R.string.article_by_author, TextUtils.isEmpty(trendingLink.authorName)? trendingLink.providerName : trendingLink.authorName)); + openLinkButton.setVisibility(View.VISIBLE); + } + + private void openLink() { + UiUtils.launchWebBrowser(getActivity(), trendingLink.url); + } +} diff --git a/mastodon/src/main/res/drawable/ic_fluent_document_one_page_24_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_document_one_page_24_filled.xml new file mode 100644 index 000000000..be2cf77c6 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_document_one_page_24_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/layout/header_trending_link_timeline.xml b/mastodon/src/main/res/layout/header_trending_link_timeline.xml new file mode 100644 index 000000000..454972a01 --- /dev/null +++ b/mastodon/src/main/res/layout/header_trending_link_timeline.xml @@ -0,0 +1,52 @@ + + + + + + + +