From 37278ff52b294da6798c1fa87717d04a8d7e45b9 Mon Sep 17 00:00:00 2001 From: sk22 Date: Fri, 13 Jan 2023 04:35:48 +0100 Subject: [PATCH] New home layout with public timelines (#288) * add dummy popup menu * add pager to home fragment * reduce pager sensitivity * remove timelines from discover fragment * add fabs to timelines * change info banner color * add back toolbar functionality * update icons on navigate * handle back press * add lists and hashtags * use tabs * improve timeline title * tweak switcher behavior * fix show new posts button appearance * hide show new posts button on reload * tweak show new posts animations * work around crash theme switch * enable disabling federated timeline --- .../requests/tags/GetFollowedHashtags.java | 4 + .../fragments/FabStatusListFragment.java | 37 ++ .../android/fragments/HomeFragment.java | 36 +- .../android/fragments/HomeTabFragment.java | 511 ++++++++++++++++++ .../fragments/HomeTimelineFragment.java | 233 +------- .../fragments/NotificationsFragment.java | 1 + .../android/fragments/ProfileFragment.java | 1 + .../android/fragments/SettingsFragment.java | 1 + .../android/fragments/StatusListFragment.java | 7 + .../fragments/discover/DiscoverFragment.java | 66 +-- .../discover/FederatedTimelineFragment.java | 4 +- .../discover/LocalTimelineFragment.java | 4 +- .../android/ui/utils/UiUtils.java | 19 + .../main/res/drawable/bg_button_new_posts.xml | 6 +- .../ic_fluent_chevron_down_16_filled.xml | 3 + .../drawable/ic_fluent_earth_24_filled.xml | 3 + .../res/drawable/ic_fluent_home_24_filled.xml | 3 + .../drawable/ic_fluent_home_24_regular.xml | 3 + .../ic_fluent_people_community_24_filled.xml | 3 + .../ic_fluent_people_list_24_filled.xml | 3 + .../main/res/layout/discover_info_banner.xml | 2 +- mastodon/src/main/res/layout/home_toolbar.xml | 56 ++ mastodon/src/main/res/menu/home_switcher.xml | 12 + mastodon/src/main/res/values/ids.xml | 2 + mastodon/src/main/res/values/strings_sk.xml | 3 + 25 files changed, 733 insertions(+), 290 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/FabStatusListFragment.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java create mode 100644 mastodon/src/main/res/drawable/ic_fluent_chevron_down_16_filled.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_earth_24_filled.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_home_24_filled.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_home_24_regular.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_people_community_24_filled.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_people_list_24_filled.xml create mode 100644 mastodon/src/main/res/layout/home_toolbar.xml create mode 100644 mastodon/src/main/res/menu/home_switcher.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/GetFollowedHashtags.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/GetFollowedHashtags.java index c38d13f1e..b347fe235 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/GetFollowedHashtags.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/tags/GetFollowedHashtags.java @@ -9,6 +9,10 @@ import org.joinmastodon.android.model.Hashtag; import java.util.List; public class GetFollowedHashtags extends HeaderPaginationRequest { + public GetFollowedHashtags() { + this(null, null, -1, null); + } + public GetFollowedHashtags(String maxID, String minID, int limit, String sinceID){ super(HttpMethod.GET, "/followed_tags", new TypeToken<>(){}); if(maxID!=null) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FabStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FabStatusListFragment.java new file mode 100644 index 000000000..c4443a2c8 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/FabStatusListFragment.java @@ -0,0 +1,37 @@ +package org.joinmastodon.android.fragments; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageButton; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.ui.utils.UiUtils; + +import me.grishka.appkit.Nav; + +public abstract class FabStatusListFragment extends StatusListFragment { + protected ImageButton fab; + + public FabStatusListFragment() { + setListLayoutId(R.layout.recycler_fragment_with_fab); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + fab = view.findViewById(R.id.fab); + fab.setOnClickListener(this::onFabClick); + fab.setOnLongClickListener(this::onFabLongClick); + } + + protected void onFabClick(View v){ + Bundle args=new Bundle(); + args.putString("account", accountID); + Nav.go(getActivity(), ComposeFragment.class, args); + } + + protected boolean onFabLongClick(View v) { + return UiUtils.pickAccountForCompose(getActivity(), accountID); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java index ca33aefd9..c4bd2b03a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java @@ -5,6 +5,7 @@ import android.app.NotificationManager; import android.graphics.Outline; import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -41,7 +42,7 @@ import me.grishka.appkit.views.FragmentRootLinearLayout; public class HomeFragment extends AppKitFragment implements OnBackPressedListener{ private FragmentRootLinearLayout content; - private HomeTimelineFragment homeTimelineFragment; + private HomeTabFragment homeTabFragment; private NotificationsFragment notificationsFragment; private DiscoverFragment searchFragment; private ProfileFragment profileFragment; @@ -65,8 +66,8 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene if(savedInstanceState==null){ Bundle args=new Bundle(); args.putString("account", accountID); - homeTimelineFragment=new HomeTimelineFragment(); - homeTimelineFragment.setArguments(args); + homeTabFragment=new HomeTabFragment(); + homeTabFragment.setArguments(args); args=new Bundle(args); args.putBoolean("noAutoLoad", true); searchFragment=new DiscoverFragment(); @@ -110,7 +111,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene if(savedInstanceState==null){ getChildFragmentManager().beginTransaction() - .add(R.id.fragment_wrap, homeTimelineFragment) + .add(R.id.fragment_wrap, homeTabFragment) .add(R.id.fragment_wrap, searchFragment).hide(searchFragment) .add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment) .add(R.id.fragment_wrap, profileFragment).hide(profileFragment) @@ -136,16 +137,16 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene @Override public void onViewStateRestored(Bundle savedInstanceState){ super.onViewStateRestored(savedInstanceState); - if(savedInstanceState==null || homeTimelineFragment!=null) + if(savedInstanceState==null || homeTabFragment !=null) return; - homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment"); + homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment"); searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment"); notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment"); profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment"); currentTab=savedInstanceState.getInt("selectedTab"); Fragment current=fragmentForTab(currentTab); getChildFragmentManager().beginTransaction() - .hide(homeTimelineFragment) + .hide(homeTabFragment) .hide(searchFragment) .hide(notificationsFragment) .hide(profileFragment) @@ -180,7 +181,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom())); } WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0); - homeTimelineFragment.onApplyWindowInsets(topOnlyInsets); + homeTabFragment.onApplyWindowInsets(topOnlyInsets); searchFragment.onApplyWindowInsets(topOnlyInsets); notificationsFragment.onApplyWindowInsets(topOnlyInsets); profileFragment.onApplyWindowInsets(topOnlyInsets); @@ -188,7 +189,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene private Fragment fragmentForTab(@IdRes int tab){ if(tab==R.id.tab_home){ - return homeTimelineFragment; + return homeTabFragment; }else if(tab==R.id.tab_search){ return searchFragment; }else if(tab==R.id.tab_notifications){ @@ -248,17 +249,24 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene tabBar.selectTab(R.id.tab_home); onTabSelected(R.id.tab_home); return true; + } else { + return homeTabFragment.onBackPressed(); } - return false; } @Override public void onSaveInstanceState(Bundle outState){ super.onSaveInstanceState(outState); outState.putInt("selectedTab", currentTab); - getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment); - getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment); - getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment); - getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment); + try { + getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment); + getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment); + getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment); + getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment); + } catch (IllegalStateException ex) { + // java.lang.IllegalStateException: Fragment HomeTabFragment{3447cad} is not currently in the FragmentManager + // no idea how to fix this :/ + Log.e(HomeFragment.class.getSimpleName(), ex.getMessage()); + } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java new file mode 100644 index 000000000..d6ef6734a --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java @@ -0,0 +1,511 @@ +package org.joinmastodon.android.fragments; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toolbar; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.widget.ViewPager2; + +import com.squareup.otto.Subscribe; + +import org.joinmastodon.android.E; +import org.joinmastodon.android.GlobalUserPreferences; +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.announcements.GetAnnouncements; +import org.joinmastodon.android.api.requests.lists.GetLists; +import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags; +import org.joinmastodon.android.events.SelfUpdateStateChangedEvent; +import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment; +import org.joinmastodon.android.fragments.discover.LocalTimelineFragment; +import org.joinmastodon.android.model.Announcement; +import org.joinmastodon.android.model.Hashtag; +import org.joinmastodon.android.model.HeaderPaginationList; +import org.joinmastodon.android.model.ListTimeline; +import org.joinmastodon.android.ui.SimpleViewHolder; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.updater.GithubSelfUpdater; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; +import me.grishka.appkit.fragments.BaseRecyclerFragment; +import me.grishka.appkit.fragments.OnBackPressedListener; +import me.grishka.appkit.utils.CubicBezierInterpolator; +import me.grishka.appkit.utils.V; + +public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener { + private static final int ANNOUNCEMENTS_RESULT = 654; + + private String accountID; + private MenuItem announcements; +// private ImageView toolbarLogo; + private Button toolbarShowNewPostsBtn; + private boolean newPostsBtnShown; + private AnimatorSet currentNewPostsAnim; + private ViewPager2 pager; + private final List fragments = new ArrayList<>(); + private final List tabViews = new ArrayList<>(); + private FrameLayout toolbarFrame; + private ImageView timelineIcon; + private ImageView collapsedChevron; + private TextView timelineTitle; + private PopupMenu switcherPopup; + private final Map listItems = new HashMap<>(); + private final Map hashtagsItems = new HashMap<>(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + accountID = getArguments().getString("account"); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + setHasOptionsMenu(true); + } + + @Override + public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { + FrameLayout view = new FrameLayout(getContext()); + pager = new ViewPager2(getContext()); + toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false); + + if (fragments.size() == 0) { + Bundle args = new Bundle(); + args.putString("account", accountID); + args.putBoolean("__is_tab", true); + + fragments.add(new HomeTimelineFragment()); + fragments.add(new LocalTimelineFragment()); + if (GlobalUserPreferences.showFederatedTimeline) fragments.add(new FederatedTimelineFragment()); + + FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); + for (int i = 0; i < fragments.size(); i++) { + fragments.get(i).setArguments(args); + FrameLayout tabView = new FrameLayout(getActivity()); + tabView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + tabView.setVisibility(View.GONE); + tabView.setId(i + 1); + transaction.add(i + 1, fragments.get(i)); + view.addView(tabView); + tabViews.add(tabView); + } + transaction.commit(); + } + + view.addView(pager, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + return view; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + + timelineIcon = toolbarFrame.findViewById(R.id.timeline_icon); + timelineTitle = toolbarFrame.findViewById(R.id.timeline_title); + collapsedChevron = toolbarFrame.findViewById(R.id.collapsed_chevron); + View switcher = toolbarFrame.findViewById(R.id.switcher_btn); + switcherPopup = new PopupMenu(getContext(), switcher); + switcherPopup.inflate(R.menu.home_switcher); + switcherPopup.setOnMenuItemClickListener(this::onSwitcherItemSelected); + UiUtils.enablePopupMenuIcons(getContext(), switcherPopup); + switcher.setOnClickListener(v->{ + updateSwitcherMenu(); + switcherPopup.show(); + }); + View.OnTouchListener listener = switcherPopup.getDragToOpenListener(); + switcher.setOnTouchListener((v, m)-> { + updateSwitcherMenu(); + return listener.onTouch(v, m); + }); + + UiUtils.reduceSwipeSensitivity(pager); + pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe); + pager.setAdapter(new HomePagerAdapter()); + pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){ + @Override + public void onPageSelected(int position){ + updateSwitcherIcon(position); + if (position==0) return; + hideNewPostsButton(); + if (fragments.get(position) instanceof BaseRecyclerFragment page){ + if(!page.loaded && !page.isDataLoading()) page.loadData(); + } + } + }); + + updateToolbarLogo(); + + if(GithubSelfUpdater.needSelfUpdating()){ + E.register(this); + updateUpdateState(GithubSelfUpdater.getInstance().getState()); + } + + new GetLists().setCallback(new Callback<>() { + @Override + public void onSuccess(List lists) { + addListsToSwitcher(lists); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(getContext()); + } + }).exec(accountID); + + new GetFollowedHashtags().setCallback(new Callback<>() { + @Override + public void onSuccess(HeaderPaginationList hashtags) { + addHashtagsToSwitcher(hashtags); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(getContext()); + } + }).exec(accountID); + } + + public void updateToolbarLogo(){ + Toolbar toolbar = getToolbar(); + ViewParent parentView = toolbarFrame.getParent(); + if (parentView == toolbar) return; + if (parentView instanceof Toolbar parentToolbar) parentToolbar.removeView(toolbarFrame); + toolbar.addView(toolbarFrame, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + toolbar.setOnClickListener(v->scrollToTop()); + toolbar.setNavigationContentDescription(R.string.back); + toolbar.setContentInsetsAbsolute(0, toolbar.getContentInsetRight()); + + updateSwitcherIcon(pager.getCurrentItem()); + +// toolbarLogo=new ImageView(getActivity()); +// toolbarLogo.setScaleType(ImageView.ScaleType.CENTER); +// toolbarLogo.setImageResource(R.drawable.logo); +// toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary))); + + toolbarShowNewPostsBtn=toolbarFrame.findViewById(R.id.show_new_posts_btn); + toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors()); + if(Build.VERSION.SDK_INT { + // toolbar frame goes from screen edge to beginning of right-aligned option buttons. + // centering button by applying the same space on the left + int padding = toolbar.getWidth() - toolbarFrame.getWidth(); + ((FrameLayout) toolbarShowNewPostsBtn.getParent()).setPadding(padding, 0, 0, 0); + }); + + if(newPostsBtnShown){ + toolbarShowNewPostsBtn.setVisibility(View.VISIBLE); + collapsedChevron.setVisibility(View.VISIBLE); + collapsedChevron.setAlpha(1f); + timelineTitle.setVisibility(View.GONE); + timelineTitle.setAlpha(0f); + }else{ + toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE); + toolbarShowNewPostsBtn.setAlpha(0f); + collapsedChevron.setVisibility(View.GONE); + collapsedChevron.setAlpha(0f); + toolbarShowNewPostsBtn.setScaleX(.8f); + toolbarShowNewPostsBtn.setScaleY(.8f); + timelineTitle.setVisibility(View.VISIBLE); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + inflater.inflate(R.menu.home, menu); + announcements = menu.findItem(R.id.announcements); + + new GetAnnouncements(false).setCallback(new Callback<>() { + @Override + public void onSuccess(List result) { + boolean hasUnread = result.stream().anyMatch(a -> !a.read); + announcements.setIcon(hasUnread ? R.drawable.ic_announcements_24_badged : R.drawable.ic_fluent_megaphone_24_regular); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(getActivity()); + } + }).exec(accountID); + } + + private void addListsToSwitcher(List lists) { + if (lists.size() == 0) return; + for (int i = 0; i < lists.size(); i++) { + ListTimeline list = lists.get(i); + int id = View.generateViewId(); + listItems.put(id, list); + } + updateSwitcherMenu(); + } + + private void addHashtagsToSwitcher(List hashtags) { + if (hashtags.size() == 0) return; + for (int i = 0; i < hashtags.size(); i++) { + Hashtag tag = hashtags.get(i); + int id = View.generateViewId(); + hashtagsItems.put(id, tag); + } + updateSwitcherMenu(); + } + + private void updateSwitcherMenu() { + Context context = getContext(); + switcherPopup.getMenu().findItem(R.id.federated).setVisible(GlobalUserPreferences.showFederatedTimeline); + + if (!listItems.isEmpty()) { + MenuItem listsItem = switcherPopup.getMenu().findItem(R.id.lists); + listsItem.setVisible(true); + SubMenu listsMenu = listsItem.getSubMenu(); + listsMenu.clear(); + listItems.forEach((id, list) -> { + MenuItem item = listsMenu.add(Menu.NONE, id, Menu.NONE, list.title); + item.setIcon(R.drawable.ic_fluent_people_list_24_regular); + UiUtils.insetPopupMenuIcon(context, item); + }); + } + + if (!hashtagsItems.isEmpty()) { + MenuItem hashtagsItem = switcherPopup.getMenu().findItem(R.id.followed_hashtags); + hashtagsItem.setVisible(true); + SubMenu hashtagsMenu = hashtagsItem.getSubMenu(); + hashtagsMenu.clear(); + hashtagsItems.forEach((id, hashtag) -> { + MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name); + item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular); + UiUtils.insetPopupMenuIcon(context, item); + }); + } + } + + private boolean onSwitcherItemSelected(MenuItem item) { + int id = item.getItemId(); + ListTimeline list; + Hashtag hashtag; + if (id == R.id.home) { + navigateTo(0); + return true; + } else if (id == R.id.local) { + navigateTo(1); + return true; + } else if (id == R.id.federated) { + navigateTo(2); + return true; + } else if ((list = listItems.get(id)) != null) { + Bundle args = new Bundle(); + args.putString("account", accountID); + args.putString("listID", list.id); + args.putString("listTitle", list.title); + args.putInt("repliesPolicy", list.repliesPolicy.ordinal()); + Nav.go(getActivity(), ListTimelineFragment.class, args); + } else if ((hashtag = hashtagsItems.get(id)) != null) { + UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag.name, hashtag.following); + } + return false; + } + + private void navigateTo(int i) { + pager.setCurrentItem(i); + updateSwitcherIcon(i); + } + + private void updateSwitcherIcon(int i) { + timelineIcon.setImageResource(switch (i) { + default -> R.drawable.ic_fluent_home_24_regular; + case 1 -> R.drawable.ic_fluent_people_community_24_regular; + case 2 -> R.drawable.ic_fluent_earth_24_regular; + }); + timelineTitle.setText(switch (i) { + default -> R.string.sk_timeline_home; + case 1 -> R.string.sk_timeline_local; + case 2 -> R.string.sk_timeline_federated; + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + Bundle args=new Bundle(); + args.putString("account", accountID); + if (item.getItemId() == R.id.settings) Nav.go(getActivity(), SettingsFragment.class, args); + if (item.getItemId() == R.id.announcements) { + Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this); + } + return true; + } + + @Override + public void scrollToTop(){ + ((ScrollableToTop) fragments.get(pager.getCurrentItem())).scrollToTop(); + } + + public void hideNewPostsButton(){ + if(!newPostsBtnShown) + return; + newPostsBtnShown=false; + if(currentNewPostsAnim!=null){ + currentNewPostsAnim.cancel(); + } + timelineTitle.setVisibility(View.VISIBLE); + AnimatorSet set=new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(timelineTitle, View.ALPHA, 1f), + ObjectAnimator.ofFloat(timelineTitle, View.SCALE_X, 1f), + ObjectAnimator.ofFloat(timelineTitle, View.SCALE_Y, 1f), + ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f), + ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f), + ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f), + ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 0f), + ObjectAnimator.ofFloat(collapsedChevron, View.SCALE_X, .8f), + ObjectAnimator.ofFloat(collapsedChevron, View.SCALE_Y, .8f) + ); + set.setDuration(GlobalUserPreferences.reduceMotion ? 0 : 300); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE); + collapsedChevron.setVisibility(View.GONE); + currentNewPostsAnim=null; + } + }); + currentNewPostsAnim=set; + set.start(); + } + + public void showNewPostsButton(){ + if(newPostsBtnShown) + return; + newPostsBtnShown=true; + if(currentNewPostsAnim!=null){ + currentNewPostsAnim.cancel(); + } + toolbarShowNewPostsBtn.setVisibility(View.VISIBLE); + collapsedChevron.setVisibility(View.VISIBLE); + AnimatorSet set=new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(timelineTitle, View.ALPHA, 0f), + ObjectAnimator.ofFloat(timelineTitle, View.SCALE_X, .8f), + ObjectAnimator.ofFloat(timelineTitle, View.SCALE_Y, .8f), + ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f), + ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f), + ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f), + ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 1f), + ObjectAnimator.ofFloat(collapsedChevron, View.SCALE_X, 1f), + ObjectAnimator.ofFloat(collapsedChevron, View.SCALE_Y, 1f) + ); + set.setDuration(GlobalUserPreferences.reduceMotion ? 0 : 300); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + timelineTitle.setVisibility(View.GONE); + currentNewPostsAnim=null; + } + }); + currentNewPostsAnim=set; + set.start(); + } + + public boolean isNewPostsBtnShown() { + return newPostsBtnShown; + } + + private void onNewPostsBtnClick(View view) { + if(newPostsBtnShown){ + hideNewPostsButton(); + scrollToTop(); + } + } + + @Override + public void onFragmentResult(int reqCode, boolean noMoreUnread, Bundle result){ + if (reqCode == ANNOUNCEMENTS_RESULT && noMoreUnread) { + announcements.setIcon(R.drawable.ic_fluent_megaphone_24_regular); + } + } + + private void updateUpdateState(GithubSelfUpdater.UpdateState state){ + if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING) + getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged); + } + + @Subscribe + public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){ + updateUpdateState(ev.state); + } + + @Override + public boolean onBackPressed(){ + if(pager.getCurrentItem() > 0){ + navigateTo(0); + return true; + } + return false; + } + + @Override + public void onDestroyView(){ + super.onDestroyView(); + if(GithubSelfUpdater.needSelfUpdating()){ + E.unregister(this); + } + } + + private class HomePagerAdapter extends RecyclerView.Adapter { + @NonNull + @Override + public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FrameLayout tabView = tabViews.get(viewType % getItemCount()); + ((ViewGroup)tabView.getParent()).removeView(tabView); + tabView.setVisibility(View.VISIBLE); + return new SimpleViewHolder(tabView); + } + + @Override + public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){} + + @Override + public int getItemCount(){ + return fragments.size(); + } + + @Override + public int getItemViewType(int position){ + return position; + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java index 3ddf32ebb..0b2e43884 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java @@ -1,47 +1,21 @@ package org.joinmastodon.android.fragments; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.app.Activity; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.os.Build; import android.os.Bundle; -import android.view.Gravity; -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.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.Toolbar; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -import com.squareup.otto.Subscribe; - -import org.joinmastodon.android.E; import org.joinmastodon.android.GlobalUserPreferences; -import org.joinmastodon.android.R; -import org.joinmastodon.android.api.requests.announcements.GetAnnouncements; import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline; import org.joinmastodon.android.api.session.AccountSessionManager; -import org.joinmastodon.android.events.SelfUpdateStateChangedEvent; import org.joinmastodon.android.events.StatusCreatedEvent; -import org.joinmastodon.android.model.Announcement; import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; -import org.joinmastodon.android.ui.utils.UiUtils; -import org.joinmastodon.android.updater.GithubSelfUpdater; import org.joinmastodon.android.utils.StatusFilterPredicate; import java.util.Collections; @@ -50,33 +24,19 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.SimpleCallback; -import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.V; -public class HomeTimelineFragment extends StatusListFragment{ - private static final int ANNOUNCEMENTS_RESULT = 654; - - private ImageButton fab; - private ImageView toolbarLogo; - private Button toolbarShowNewPostsBtn; - private boolean newPostsBtnShown; - private AnimatorSet currentNewPostsAnim; - private MenuItem announcements; - +public class HomeTimelineFragment extends FabStatusListFragment { + private HomeTabFragment parent; private String maxID; - public HomeTimelineFragment(){ - setListLayoutId(R.layout.recycler_fragment_with_fab); - } - @Override public void onAttach(Activity activity){ super.onAttach(activity); - setHasOptionsMenu(true); + if (getParentFragment() instanceof HomeTabFragment home) parent = home; loadData(); } @@ -108,67 +68,15 @@ public class HomeTimelineFragment extends StatusListFragment{ @Override public void onViewCreated(View view, Bundle savedInstanceState){ super.onViewCreated(view, savedInstanceState); - fab=view.findViewById(R.id.fab); - fab.setOnClickListener(this::onFabClick); - fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID)); - updateToolbarLogo(); list.addOnScrollListener(new RecyclerView.OnScrollListener(){ @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ - if(newPostsBtnShown && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){ - hideNewPostsButton(); + if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){ + parent.hideNewPostsButton(); } } }); - - if(GithubSelfUpdater.needSelfUpdating()){ - E.register(this); - updateUpdateState(GithubSelfUpdater.getInstance().getState()); - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ - inflater.inflate(R.menu.home, menu); - announcements = menu.findItem(R.id.announcements); - - new GetAnnouncements(false).setCallback(new Callback<>() { - @Override - public void onSuccess(List result) { - boolean hasUnread = result.stream().anyMatch(a -> !a.read); - announcements.setIcon(hasUnread ? R.drawable.ic_announcements_24_badged : R.drawable.ic_fluent_megaphone_24_regular); - } - - @Override - public void onError(ErrorResponse error) { - error.showToast(getActivity()); - } - }).exec(accountID); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item){ - Bundle args=new Bundle(); - args.putString("account", accountID); - if (item.getItemId() == R.id.settings) Nav.go(getActivity(), SettingsFragment.class, args); - if (item.getItemId() == R.id.announcements) { - Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this); - } - return true; - } - - @Override - public void onFragmentResult(int reqCode, boolean noMoreUnread, Bundle result){ - if (reqCode == ANNOUNCEMENTS_RESULT && noMoreUnread) { - announcements.setIcon(R.drawable.ic_fluent_megaphone_24_regular); - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig){ - super.onConfigurationChanged(newConfig); - updateToolbarLogo(); } @Override @@ -187,12 +95,6 @@ public class HomeTimelineFragment extends StatusListFragment{ prependItems(Collections.singletonList(ev.status), true); } - private void onFabClick(View v){ - Bundle args=new Bundle(); - args.putString("account", accountID); - Nav.go(getActivity(), ComposeFragment.class, args); - } - private void loadNewPosts(){ if (!GlobalUserPreferences.loadNewPosts) return; dataLoading=true; @@ -221,7 +123,7 @@ public class HomeTimelineFragment extends StatusListFragment{ toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList()); if(!toAdd.isEmpty()){ prependItems(toAdd, true); - showNewPostsButton(); + if (parent != null) parent.showNewPostsButton(); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false); } } @@ -337,131 +239,10 @@ public class HomeTimelineFragment extends StatusListFragment{ currentRequest=null; dataLoading=false; } + if (parent != null) parent.hideNewPostsButton(); super.onRefresh(); } - private void updateToolbarLogo(){ - toolbarLogo=new ImageView(getActivity()); - toolbarLogo.setScaleType(ImageView.ScaleType.CENTER); - toolbarLogo.setImageResource(R.drawable.logo); - toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary))); - - toolbarShowNewPostsBtn=new Button(getActivity()); - toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium); - toolbarShowNewPostsBtn.setTextColor(0xffffffff); - toolbarShowNewPostsBtn.setStateListAnimator(null); - toolbarShowNewPostsBtn.setBackgroundResource(R.drawable.bg_button_new_posts); - toolbarShowNewPostsBtn.setText(R.string.see_new_posts); - toolbarShowNewPostsBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_fluent_arrow_up_16_filled, 0, 0, 0); - toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors()); - toolbarShowNewPostsBtn.setCompoundDrawablePadding(V.dp(8)); - if(Build.VERSION.SDK_INT{ adapter.notifyItemRangeRemoved(index, lastIndex-index); } + @Override + public void onConfigurationChanged(Configuration newConfig){ + super.onConfigurationChanged(newConfig); + if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo(); + } + public class EventListener{ @Subscribe diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java index 6dfea2f68..a975f37bb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java @@ -53,14 +53,10 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, private DiscoverNewsFragment newsFragment; private DiscoverAccountsFragment accountsFragment; private SearchFragment searchFragment; - private LocalTimelineFragment localTimelineFragment; - private FederatedTimelineFragment federatedTimelineFragment; private String accountID; private Runnable searchDebouncer=this::onSearchChangedDebounced; - private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline; - @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -78,18 +74,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, tabLayout=view.findViewById(R.id.tabbar); pager=view.findViewById(R.id.pager); - tabViews=new FrameLayout[noFederated ? 5 : 6]; + tabViews=new FrameLayout[4]; for(int i=0;i 0 ? i + 1 : i; - tabView.setId(switch(switchIndex){ - case 0 -> R.id.discover_local_timeline; - case 1 -> R.id.discover_federated_timeline; - case 2 -> R.id.discover_hashtags; - case 3 -> R.id.discover_posts; - case 4 -> R.id.discover_news; - case 5 -> R.id.discover_users; - default -> throw new IllegalStateException("Unexpected value: "+switchIndex); + tabView.setId(switch(i){ + case 0 -> R.id.discover_hashtags; + case 1 -> R.id.discover_posts; + case 2 -> R.id.discover_news; + case 3 -> R.id.discover_users; + default -> throw new IllegalStateException("Unexpected value: "+i); }); tabView.setVisibility(View.GONE); view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment @@ -99,6 +92,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, tabLayout.setTabTextSize(V.dp(16)); tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)); + UiUtils.reduceSwipeSensitivity(pager); pager.setOffscreenPageLimit(4); pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe); pager.setAdapter(new DiscoverPagerAdapter()); @@ -115,7 +109,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, } }); - if(localTimelineFragment==null){ + if(hashtagsFragment==null){ Bundle args=new Bundle(); args.putString("account", accountID); args.putBoolean("__is_tab", true); @@ -132,36 +126,23 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, accountsFragment=new DiscoverAccountsFragment(); accountsFragment.setArguments(args); - localTimelineFragment=new LocalTimelineFragment(); - localTimelineFragment.setArguments(args); - FragmentTransaction transaction = getChildFragmentManager().beginTransaction() + getChildFragmentManager().beginTransaction() .add(R.id.discover_posts, postsFragment) - .add(R.id.discover_local_timeline, localTimelineFragment) .add(R.id.discover_hashtags, hashtagsFragment) .add(R.id.discover_news, newsFragment) - .add(R.id.discover_users, accountsFragment); - - if (!noFederated) { - federatedTimelineFragment=new FederatedTimelineFragment(); - federatedTimelineFragment.setArguments(args); - transaction.add(R.id.discover_federated_timeline, federatedTimelineFragment); - } - - transaction.commit(); + .add(R.id.discover_users, accountsFragment) + .commit(); } tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){ @Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){ - if (noFederated && position > 0) position++; tab.setText(switch(position){ - case 0 -> R.string.local_timeline; - case 1 -> R.string.sk_federated_timeline; - case 2 -> R.string.hashtags; - case 3 -> R.string.posts; - case 4 -> R.string.news; - case 5 -> R.string.for_you; + case 0 -> R.string.hashtags; + case 1 -> R.string.posts; + case 2 -> R.string.news; + case 3 -> R.string.for_you; default -> throw new IllegalStateException("Unexpected value: "+position); }); tab.view.textView.setAllCaps(true); @@ -247,8 +228,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, } public void loadData(){ - if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading) - localTimelineFragment.loadData(); + if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading) + hashtagsFragment.loadData(); } private void onSearchEditFocusChanged(View v, boolean hasFocus){ @@ -283,14 +264,11 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, } private Fragment getFragmentForPage(int page){ - if (noFederated && page > 0) page++; return switch(page){ - case 0 -> localTimelineFragment; - case 1 -> federatedTimelineFragment; - case 2 -> hashtagsFragment; - case 3 -> postsFragment; - case 4 -> newsFragment; - case 5 -> accountsFragment; + case 0 -> hashtagsFragment; + case 1 -> postsFragment; + case 2 -> newsFragment; + case 3 -> accountsFragment; default -> throw new IllegalStateException("Unexpected value: "+page); }; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java index 0943ddbff..59cae5f96 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java @@ -3,7 +3,9 @@ package org.joinmastodon.android.fragments.discover; import android.os.Bundle; import android.view.View; +import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline; +import org.joinmastodon.android.fragments.FabStatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Status; @@ -15,7 +17,7 @@ import java.util.stream.Collectors; import me.grishka.appkit.api.SimpleCallback; -public class FederatedTimelineFragment extends StatusListFragment{ +public class FederatedTimelineFragment extends FabStatusListFragment { private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE); private String maxID; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java index 76c804c37..bd1443124 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java @@ -3,7 +3,9 @@ package org.joinmastodon.android.fragments.discover; import android.os.Bundle; import android.view.View; +import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline; +import org.joinmastodon.android.fragments.FabStatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Status; @@ -15,7 +17,7 @@ import java.util.stream.Collectors; import me.grishka.appkit.api.SimpleCallback; -public class LocalTimelineFragment extends StatusListFragment{ +public class LocalTimelineFragment extends FabStatusListFragment { private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE); private String maxID; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index ccf83b905..505e52600 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -31,6 +31,7 @@ import android.provider.OpenableColumns; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; +import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuItem; @@ -87,6 +88,7 @@ import org.joinmastodon.android.ui.text.CustomEmojiSpan; import org.parceler.Parcels; import java.io.File; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; @@ -111,6 +113,8 @@ import androidx.annotation.StringRes; import androidx.browser.customtabs.CustomTabsIntent; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.widget.ViewPager2; + import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; @@ -1014,4 +1018,19 @@ public class UiUtils{ return false; } } + + // https://github.com/tuskyapp/Tusky/pull/3148 + public static void reduceSwipeSensitivity(ViewPager2 pager) { + try { + Field recyclerViewField = ViewPager2.class.getDeclaredField("mRecyclerView"); + recyclerViewField.setAccessible(true); + RecyclerView recyclerView = (RecyclerView) recyclerViewField.get(pager); + Field touchSlopField = RecyclerView.class.getDeclaredField("mTouchSlop"); + touchSlopField.setAccessible(true); + int touchSlop = touchSlopField.getInt(recyclerView); + touchSlopField.set(recyclerView, touchSlop * 3); + } catch (Exception ex) { + Log.e("reduceSwipeSensitivity", Log.getStackTraceString(ex)); + } + } } diff --git a/mastodon/src/main/res/drawable/bg_button_new_posts.xml b/mastodon/src/main/res/drawable/bg_button_new_posts.xml index 2178510e4..2c9e3691b 100644 --- a/mastodon/src/main/res/drawable/bg_button_new_posts.xml +++ b/mastodon/src/main/res/drawable/bg_button_new_posts.xml @@ -2,9 +2,9 @@ - - - + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_fluent_chevron_down_16_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_chevron_down_16_filled.xml new file mode 100644 index 000000000..2b11982e5 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_chevron_down_16_filled.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_earth_24_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_earth_24_filled.xml new file mode 100644 index 000000000..5e8923325 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_earth_24_filled.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_home_24_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_home_24_filled.xml new file mode 100644 index 000000000..0718faec9 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_home_24_filled.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_home_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_home_24_regular.xml new file mode 100644 index 000000000..e93f783b1 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_home_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_people_community_24_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_people_community_24_filled.xml new file mode 100644 index 000000000..c4eff756c --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_people_community_24_filled.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_people_list_24_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_people_list_24_filled.xml new file mode 100644 index 000000000..b51c85af7 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_people_list_24_filled.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/discover_info_banner.xml b/mastodon/src/main/res/layout/discover_info_banner.xml index 9f9939937..32f6b0b6b 100644 --- a/mastodon/src/main/res/layout/discover_info_banner.xml +++ b/mastodon/src/main/res/layout/discover_info_banner.xml @@ -8,7 +8,7 @@ android:layout_gravity="top" android:elevation="1dp" android:outlineProvider="background" - android:background="?colorWindowBackground"> + android:background="?colorBackgroundLight"> + + + + + + + + + +