Merge pull request #429 from FineFindus/feat/trending-links-timeline

feat(Discover): add Timeline to trending links
This commit is contained in:
LucasGGamerM 2024-06-16 09:44:13 -03:00 committed by GitHub
commit 0bdb23e462
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 315 additions and 1 deletions

View File

@ -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<List<Status>>{
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);
}
}

View File

@ -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,16 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
@Override
public void onClick(){
//TODO: enable timeline for all servers once 4.3.0 is released
if(getInstance().isEmpty() ||
!getInstance().get().version.contains("4.3.0")){
UiUtils.launchWebBrowser(getActivity(), item.url);
return;
}
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("trendingLink", Parcels.wrap(item));
Nav.go(getActivity(), DiscoverTrendingLinkTimelineFragment.class, args);
}
}

View File

@ -0,0 +1,210 @@
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.ComposeFragment;
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.Nav;
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<Status> 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);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
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
public boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID, trendingLink.url);
}
@Override
public void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("prefilledText", trendingLink.url);
Nav.go(getActivity(), ComposeFragment.class, args);
}
@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);
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17.75,2.007a2.25,2.25 0,0 1,2.245 2.096l0.005,0.154v15.498A2.25,2.25 0,0 1,17.904 22l-0.154,0.005H6.25a2.25,2.25 0,0 1,-2.245 -2.096L4,19.755V4.257a2.25,2.25 0,0 1,2.096 -2.245l0.154,-0.005h11.5ZM7.75,7a0.75,0.75 0,1 0,0 1.5h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5ZM7,11.75c0,0.414 0.336,0.75 0.75,0.75h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5a0.75,0.75 0,0 0,-0.75 0.75ZM7.75,15a0.75,0.75 0,1 0,0 1.5h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5Z"
android:fillColor="#212121"/>
</vector>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/button_wrap"
android:layout_marginEnd="8dp"
android:textAppearance="@style/m3_headline_small"
android:textColor="?colorM3OnSurface"
android:maxLines="3"
android:ellipsize="end"
android:minHeight="48dp"
android:gravity="center_vertical"
tools:text="Microsoft Chose Profit Over Security"/>
<FrameLayout
android:id="@+id/button_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/title"
android:layout_alignBottom="@id/title"
android:layout_alignParentEnd="true">
<Button
android:id="@+id/profile_action_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:paddingHorizontal="16dp"
android:text="@string/mo_trending_link_read"
tools:text="@string/mark_all_notifications_read" />
</FrameLayout>
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_marginTop="4dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="@string/article_by_author"/>
</RelativeLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/open_link"
android:icon="@drawable/ic_fluent_document_one_page_24_filled"
android:showAsAction="always"
android:title="@string/mo_trending_link_read"/>
</menu>

View File

@ -138,4 +138,5 @@
<string name="mo_error_display_title">Failed to display post</string>
<string name="mo_error_display_text">Something went wrong while loading this post. If the problem persists, please report it on our Issues page along with the error details.</string>
<string name="mo_error_display_copy_error_details">Copy details</string>
<string name="mo_trending_link_read">Read</string>
</resources>