Add list timelines view
* get accounts list timelines from API * display available lists in discover view tab * display list timeline
This commit is contained in:
@@ -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<List<ListTimeline>>{
|
||||||
|
public GetLists() {
|
||||||
|
super(HttpMethod.GET, "/lists", new TypeToken<>(){});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<List<Status>> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Status> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ListTimeline> 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<ListTimeline> 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<ListViewHolder>{
|
||||||
|
@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<ListTimeline> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
|
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||||
@@ -51,6 +52,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
private DiscoverAccountsFragment accountsFragment;
|
private DiscoverAccountsFragment accountsFragment;
|
||||||
private SearchFragment searchFragment;
|
private SearchFragment searchFragment;
|
||||||
private LocalTimelineFragment localTimelineFragment;
|
private LocalTimelineFragment localTimelineFragment;
|
||||||
|
private ListTimelinesFragment listTimelinesFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||||
@@ -72,7 +74,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
|
|
||||||
tabViews=new FrameLayout[5];
|
tabViews=new FrameLayout[6];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
tabView.setId(switch(i){
|
tabView.setId(switch(i){
|
||||||
@@ -81,6 +83,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
case 2 -> R.id.discover_news;
|
case 2 -> R.id.discover_news;
|
||||||
case 3 -> R.id.discover_local_timeline;
|
case 3 -> R.id.discover_local_timeline;
|
||||||
case 4 -> R.id.discover_users;
|
case 4 -> R.id.discover_users;
|
||||||
|
case 5 -> R.id.discover_lists;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||||
});
|
});
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
@@ -126,12 +129,16 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
localTimelineFragment=new LocalTimelineFragment();
|
localTimelineFragment=new LocalTimelineFragment();
|
||||||
localTimelineFragment.setArguments(args);
|
localTimelineFragment.setArguments(args);
|
||||||
|
|
||||||
|
listTimelinesFragment=new ListTimelinesFragment();
|
||||||
|
listTimelinesFragment.setArguments(args);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.discover_posts, postsFragment)
|
.add(R.id.discover_posts, postsFragment)
|
||||||
.add(R.id.discover_local_timeline, localTimelineFragment)
|
.add(R.id.discover_local_timeline, localTimelineFragment)
|
||||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||||
.add(R.id.discover_news, newsFragment)
|
.add(R.id.discover_news, newsFragment)
|
||||||
.add(R.id.discover_users, accountsFragment)
|
.add(R.id.discover_users, accountsFragment)
|
||||||
|
.add(R.id.discover_lists, listTimelinesFragment)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +151,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
case 2 -> R.string.news;
|
case 2 -> R.string.news;
|
||||||
case 3 -> R.string.local_timeline;
|
case 3 -> R.string.local_timeline;
|
||||||
case 4 -> R.string.for_you;
|
case 4 -> R.string.for_you;
|
||||||
|
case 5 -> R.string.list_timelines;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
tab.view.textView.setAllCaps(true);
|
||||||
@@ -271,6 +279,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
case 2 -> newsFragment;
|
case 2 -> newsFragment;
|
||||||
case 3 -> localTimelineFragment;
|
case 3 -> localTimelineFragment;
|
||||||
case 4 -> accountsFragment;
|
case 4 -> accountsFragment;
|
||||||
|
case 5 -> listTimelinesFragment;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,10 +45,12 @@ import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
|||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
|
import org.joinmastodon.android.fragments.ListTimelineFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
@@ -294,6 +296,14 @@ public class UiUtils{
|
|||||||
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
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){
|
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);
|
showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), onConfirmed);
|
||||||
}
|
}
|
||||||
|
|||||||
19
mastodon/src/main/res/layout/item_list_timeline.xml
Normal file
19
mastodon/src/main/res/layout/item_list_timeline.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?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="64dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/m3_title_large"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="List"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<item name="discover_news" type="id"/>
|
<item name="discover_news" type="id"/>
|
||||||
<item name="discover_users" type="id"/>
|
<item name="discover_users" type="id"/>
|
||||||
<item name="discover_local_timeline" type="id"/>
|
<item name="discover_local_timeline" type="id"/>
|
||||||
|
<item name="discover_lists" type="id"/>
|
||||||
|
|
||||||
<item name="notifications_all" type="id"/>
|
<item name="notifications_all" type="id"/>
|
||||||
<item name="notifications_mentions" type="id"/>
|
<item name="notifications_mentions" type="id"/>
|
||||||
|
|||||||
@@ -387,4 +387,5 @@
|
|||||||
<string name="privacy_policy_title">Mastodon and your privacy</string>
|
<string name="privacy_policy_title">Mastodon and your privacy</string>
|
||||||
<string name="privacy_policy_subtitle">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.</string>
|
<string name="privacy_policy_subtitle">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.</string>
|
||||||
<string name="i_agree">I Agree</string>
|
<string name="i_agree">I Agree</string>
|
||||||
|
<string name="list_timelines">Lists</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user