diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java deleted file mode 100644 index ecb73777..00000000 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.joinmastodon.android.fragments; - -import android.app.Activity; - -import org.joinmastodon.android.R; -import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses; -import org.joinmastodon.android.events.RemoveAccountPostsEvent; -import org.joinmastodon.android.model.HeaderPaginationList; -import org.joinmastodon.android.model.Status; - -import me.grishka.appkit.api.SimpleCallback; - -public class BookmarkedStatusListFragment extends StatusListFragment{ - private String nextMaxID; - - @Override - public void onAttach(Activity activity){ - super.onAttach(activity); - setTitle(R.string.bookmarks); - loadData(); - } - - @Override - protected void doLoadData(int offset, int count){ - currentRequest=new GetBookmarkedStatuses(offset==0 ? null : nextMaxID, count) - .setCallback(new SimpleCallback<>(this){ - @Override - public void onSuccess(HeaderPaginationList result){ - if(result.nextPageUri!=null) - nextMaxID=result.nextPageUri.getQueryParameter("max_id"); - else - nextMaxID=null; - onDataLoaded(result, nextMaxID!=null); - } - }) - .exec(accountID); - } - - @Override - protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){ - // no-op - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java deleted file mode 100644 index f0779e71..00000000 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.joinmastodon.android.fragments; - -import android.app.Activity; - -import org.joinmastodon.android.R; -import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses; -import org.joinmastodon.android.model.HeaderPaginationList; -import org.joinmastodon.android.model.Status; - -import me.grishka.appkit.api.SimpleCallback; - -public class FavoritedStatusListFragment extends StatusListFragment{ - private String nextMaxID; - - @Override - public void onAttach(Activity activity){ - super.onAttach(activity); - setTitle(R.string.your_favorites); - loadData(); - } - - @Override - protected void doLoadData(int offset, int count){ - currentRequest=new GetFavoritedStatuses(offset==0 ? null : nextMaxID, count) - .setCallback(new SimpleCallback<>(this){ - @Override - public void onSuccess(HeaderPaginationList result){ - if(result.nextPageUri!=null) - nextMaxID=result.nextPageUri.getQueryParameter("max_id"); - else - nextMaxID=null; - onDataLoaded(result, nextMaxID!=null); - } - }) - .exec(accountID); - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 7e6b4946..daa03f1b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -129,6 +129,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, private ProfileFeaturedFragment featuredFragment; private AccountTimelineFragment timelineFragment; private ProfileAboutFragment aboutFragment; + private SavedPostsTimelineFragment savedFragment; private TabLayout tabbar; private SwipeRefreshLayout refreshLayout; private View followersBtn, followingBtn; @@ -254,13 +255,14 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, } }; - tabViews=new FrameLayout[3]; + tabViews=new FrameLayout[4]; for(int i=0;i R.id.profile_featured; case 1 -> R.id.profile_timeline; case 2 -> R.id.profile_about; + case 3 -> R.id.profile_saved; default -> throw new IllegalStateException("Unexpected value: "+i); }); tabView.setVisibility(View.GONE); @@ -268,7 +270,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, tabViews[i]=tabView; } - pager.setOffscreenPageLimit(4); + pager.setOffscreenPageLimit(10); pager.setAdapter(new ProfilePagerAdapter()); pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels; @@ -282,6 +284,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, case 0 -> R.string.profile_featured; case 1 -> R.string.profile_timeline; case 2 -> R.string.profile_about; + case 3 -> R.string.profile_saved_posts; default -> throw new IllegalStateException(); })); tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){ @@ -316,6 +319,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, featuredFragment=(ProfileFeaturedFragment) getChildFragmentManager().getFragment(savedInstanceState, "featured"); timelineFragment=(AccountTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "timeline"); aboutFragment=(ProfileAboutFragment) getChildFragmentManager().getFragment(savedInstanceState, "about"); + savedFragment=(SavedPostsTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "saved"); } if(loaded){ @@ -433,6 +437,9 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, aboutFragment=new ProfileAboutFragment(); aboutFragment.setFields(fields); } + if(savedFragment==null && isOwnProfile){ + savedFragment=SavedPostsTimelineFragment.newInstance(accountID, account, false); + } pager.getAdapter().notifyDataSetChanged(); pager.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ @Override @@ -508,6 +515,8 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, getChildFragmentManager().putFragment(outState, "timeline", timelineFragment); if(aboutFragment.isAdded()) getChildFragmentManager().putFragment(outState, "about", aboutFragment); + if(savedFragment!=null && savedFragment.isAdded()) + getChildFragmentManager().putFragment(outState, "saved", savedFragment); } @Override @@ -751,14 +760,6 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, }) .wrapProgress(getActivity(), R.string.loading, false) .exec(accountID); - }else if(id==R.id.bookmarks){ - Bundle args=new Bundle(); - args.putString("account", accountID); - Nav.go(getActivity(), BookmarkedStatusListFragment.class, args); - }else if(id==R.id.favorites){ - Bundle args=new Bundle(); - args.putString("account", accountID); - Nav.go(getActivity(), FavoritedStatusListFragment.class, args); }else if(id==R.id.save){ if(isInEditMode) saveAndExitEditMode(); @@ -930,6 +931,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, case 0 -> featuredFragment; case 1 -> timelineFragment; case 2 -> aboutFragment; + case 3 -> savedFragment; default -> throw new IllegalStateException(); }; } @@ -993,7 +995,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, pager.setUserInputEnabled(false); actionButton.setText(R.string.save_changes); pager.setCurrentItem(2); - for(int i=0;i<3;i++){ + for(int i=0;i<4;i++){ tabbar.getTabAt(i).view.setEnabled(false); } Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate(); @@ -1070,7 +1072,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, invalidateOptionsMenu(); actionButton.setText(R.string.edit_profile); - for(int i=0;i<3;i++){ + for(int i=0;i<4;i++){ tabbar.getTabAt(i).view.setEnabled(true); } pager.setUserInputEnabled(true); @@ -1339,7 +1341,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop, @Override public int getItemCount(){ - return loaded ? 3 : 0; + return loaded ? (isOwnProfile ? 4 : 3) : 0; } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SavedPostsTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SavedPostsTimelineFragment.java new file mode 100644 index 00000000..e81fa8af --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SavedPostsTimelineFragment.java @@ -0,0 +1,164 @@ +package org.joinmastodon.android.fragments; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses; +import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.RemoveAccountPostsEvent; +import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.HeaderPaginationList; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.drawables.EmptyDrawable; +import org.joinmastodon.android.ui.views.FilterChipView; +import org.parceler.Parcels; + +import androidx.recyclerview.widget.RecyclerView; +import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.utils.MergeRecyclerAdapter; +import me.grishka.appkit.utils.SingleViewRecyclerAdapter; +import me.grishka.appkit.utils.V; + +public class SavedPostsTimelineFragment extends StatusListFragment{ + private Account user; + private Mode mode; + private HorizontalScrollView filtersBar; + private FilterChipView favoritesChip, bookmarksChip; + + public SavedPostsTimelineFragment(){ + setListLayoutId(R.layout.recycler_fragment_no_refresh); + } + + public static SavedPostsTimelineFragment newInstance(String accountID, Account profileAccount, boolean load){ + SavedPostsTimelineFragment f=new SavedPostsTimelineFragment(); + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("profileAccount", Parcels.wrap(profileAccount)); + if(!load) + args.putBoolean("noAutoLoad", true); + args.putBoolean("__is_tab", true); + f.setArguments(args); + return f; + } + + @Override + public void onAttach(Activity activity){ + user=Parcels.unwrap(getArguments().getParcelable("profileAccount")); + mode=Mode.FAVORITES; + super.onAttach(activity); + } + + @Override + protected void doLoadData(int offset, int count){ + currentRequest=(switch(mode){ + case FAVORITES -> new GetFavoritedStatuses(offset>0 ? getMaxID() : null, count); + case BOOKMARKS -> new GetBookmarkedStatuses(offset>0 ? getMaxID() : null, count); + }).setCallback(new SimpleCallback<>(this){ + @Override + public void onSuccess(HeaderPaginationList result){ + if(getActivity()==null) + return; + onDataLoaded(result, result.nextPageUri!=null); + } + }).exec(accountID); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + view.setBackground(null); // prevents unnecessary overdraw + } + + @Override + protected void onShown(){ + super.onShown(); + if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) + loadData(); + } + + protected void onStatusCreated(Status status){ + // no-op + } + + @Override + protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){ + // no-op + } + + @Override + protected RecyclerView.Adapter getAdapter(){ + filtersBar=new HorizontalScrollView(getActivity()); + LinearLayout filtersLayout=new LinearLayout(getActivity()); + filtersBar.addView(filtersLayout); + filtersLayout.setOrientation(LinearLayout.HORIZONTAL); + filtersLayout.setPadding(V.dp(16), 0, V.dp(16), V.dp(8)); + filtersLayout.setDividerDrawable(new EmptyDrawable(V.dp(8), 1)); + filtersLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); + + favoritesChip=new FilterChipView(getActivity()); + favoritesChip.setText(R.string.your_favorites); + favoritesChip.setTag(Mode.FAVORITES); + favoritesChip.setSelected(mode==Mode.FAVORITES); + favoritesChip.setOnClickListener(this::onFilterClick); + filtersLayout.addView(favoritesChip); + + bookmarksChip=new FilterChipView(getActivity()); + bookmarksChip.setText(R.string.bookmarks); + bookmarksChip.setTag(Mode.BOOKMARKS); + bookmarksChip.setSelected(mode==Mode.BOOKMARKS); + bookmarksChip.setOnClickListener(this::onFilterClick); + filtersLayout.addView(bookmarksChip); + + View banner=getActivity().getLayoutInflater().inflate(R.layout.discover_info_banner, list, false); + TextView text=banner.findViewById(R.id.banner_text); + text.setText(R.string.profile_saved_posts_explanation); + ImageView icon=banner.findViewById(R.id.icon); + icon.setImageResource(R.drawable.ic_lock_24px); + + MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter(); + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(banner)); + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(filtersBar)); + mergeAdapter.addAdapter(super.getAdapter()); + return mergeAdapter; + } + + private FilterChipView getViewForMode(Mode mode){ + return switch(mode){ + case FAVORITES -> favoritesChip; + case BOOKMARKS -> bookmarksChip; + }; + } + + private void onFilterClick(View v){ + Mode newMode=(Mode) v.getTag(); + if(newMode==mode) + return; + if(currentRequest!=null){ + currentRequest.cancel(); + currentRequest=null; + } + getViewForMode(mode).setSelected(false); + mode=newMode; + v.setSelected(true); + data.clear(); + preloadedData.clear(); + int size=displayItems.size(); + displayItems.clear(); + adapter.notifyItemRangeRemoved(0, size); + loaded=false; + dataLoading=true; + doLoadData(); + } + + private enum Mode{ + FAVORITES, + BOOKMARKS + } +} diff --git a/mastodon/src/main/res/drawable/ic_bookmark_24px.xml b/mastodon/src/main/res/drawable/ic_bookmark_24px.xml deleted file mode 100644 index 53de975b..00000000 --- a/mastodon/src/main/res/drawable/ic_bookmark_24px.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/mastodon/src/main/res/menu/profile_own.xml b/mastodon/src/main/res/menu/profile_own.xml index 309ff890..5dd866a7 100644 --- a/mastodon/src/main/res/menu/profile_own.xml +++ b/mastodon/src/main/res/menu/profile_own.xml @@ -1,7 +1,4 @@ - - - \ No newline at end of file diff --git a/mastodon/src/main/res/values/colors_masterial.xml b/mastodon/src/main/res/values/colors_masterial.xml index 88fce83a..56572a54 100644 --- a/mastodon/src/main/res/values/colors_masterial.xml +++ b/mastodon/src/main/res/values/colors_masterial.xml @@ -3,8 +3,8 @@ #4000DD #FFFFFF - #6648FF - #FFFFFF + #EBDDFF + #230F46 #5D51AF #FFFFFF #B0A5FF diff --git a/mastodon/src/main/res/values/ids.xml b/mastodon/src/main/res/values/ids.xml index 89418b85..43987abd 100644 --- a/mastodon/src/main/res/values/ids.xml +++ b/mastodon/src/main/res/values/ids.xml @@ -5,7 +5,8 @@ - + + diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index 8c125ed9..a344800f 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -310,7 +310,7 @@ Bookmark Remove bookmark Bookmarks - Your favorites + Favorites Welcome back Log in with the server where you created your account. Server URL @@ -829,6 +829,8 @@ Followed by %s Followed by %s and %s + Saved + Your saved posts are only visible to you. Followed by %1$s, %2$s, and %3$,d other Followed by %1$s, %2$s, and %3$,d others