Add ability to scroll to top by tab click at the Account activity (#1146)

* Issue: tuskyapp#1078
Add ability to scroll to top by tab click at the Account activity

* Fix issue with scroll tabs other than current

* Update scroll on click behavior

* Update code formatting

* Remove unused code

* Move tab click listener from Fragments to Activities
This commit is contained in:
pandasoft0 2019-04-08 16:40:16 +03:00 committed by Konrad Pozniak
parent a2fa49aafb
commit 01234bb94b
8 changed files with 146 additions and 108 deletions

View file

@ -41,12 +41,14 @@ import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.keylesspalace.tusky.adapter.AccountFieldAdapter
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Relationship
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.pager.AccountPagerAdapter
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewmodel.AccountViewModel
@ -104,6 +106,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
REQUESTED
}
private var adapter: AccountPagerAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -174,7 +178,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
// Add a listener to change the toolbar icon color when it enters/exits its collapsed state.
accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
@AttrRes var priorAttribute = R.attr.account_toolbar_icon_tint_uncollapsed
@AttrRes
var priorAttribute = R.attr.account_toolbar_icon_tint_uncollapsed
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
@ -250,9 +255,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
accountFieldList.adapter = accountFieldAdapter
// Setup the tabs and timeline pager.
val adapter = AccountPagerAdapter(supportFragmentManager, accountId)
adapter = AccountPagerAdapter(supportFragmentManager, accountId)
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media))
adapter.setPageTitles(pageTitles)
adapter?.setPageTitles(pageTitles)
accountFragmentViewPager.pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
val pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
R.drawable.tab_page_margin_dark)
@ -260,7 +265,18 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
accountFragmentViewPager.adapter = adapter
accountFragmentViewPager.offscreenPageLimit = 2
accountTabLayout.setupWithViewPager(accountFragmentViewPager)
accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
tab?.position?.let {
(adapter?.getFragment(tab.position) as? ReselectableFragment)?.onReselect()
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabSelected(tab: TabLayout.Tab?) {}
})
val accountListClickListener = { v: View ->
val type = when (v.id) {
R.id.accountFollowers -> AccountListActivity.Type.FOLLOWERS

View file

@ -16,20 +16,25 @@
package com.keylesspalace.tusky;
import androidx.lifecycle.Lifecycle;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import androidx.emoji.text.EmojiCompat;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AlertDialog;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
@ -44,6 +49,7 @@ import com.keylesspalace.tusky.components.conversation.ConversationsRepository;
import com.keylesspalace.tusky.db.AccountEntity;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.ReselectableFragment;
import com.keylesspalace.tusky.pager.MainPagerAdapter;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.NotificationHelper;
@ -114,6 +120,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
private ViewPager viewPager;
private int notificationTabPosition;
private MainPagerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -209,6 +216,12 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (adapter != null) {
Fragment fragment = adapter.getFragment(tab.getPosition());
if (fragment instanceof ReselectableFragment) {
((ReselectableFragment) fragment).onReselect();
}
}
}
});
@ -410,7 +423,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
private void setupTabs(boolean selectNotificationTab) {
List<TabData> tabs = accountManager.getActiveAccount().getTabPreferences();
MainPagerAdapter adapter = new MainPagerAdapter(tabs, getSupportFragmentManager());
adapter = new MainPagerAdapter(tabs, getSupportFragmentManager());
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
@ -599,5 +612,4 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
public AndroidInjector<Fragment> supportFragmentInjector() {
return fragmentInjector;
}
}

View file

@ -34,6 +34,7 @@ import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.fragment.SFragment
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.ThemeUtils
@ -41,7 +42,7 @@ import com.keylesspalace.tusky.util.hide
import kotlinx.android.synthetic.main.fragment_timeline.*
import javax.inject.Inject
class ConversationsFragment : SFragment(), StatusActionListener, Injectable {
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
@Inject
lateinit var viewModelFactory: ViewModelFactory
@ -52,6 +53,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable {
private lateinit var adapter: ConversationAdapter
private var layoutManager: LinearLayoutManager? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
viewModel = ViewModelProviders.of(this, viewModelFactory)[ConversationsViewModel::class.java]
@ -70,7 +73,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable {
adapter = ConversationAdapter(useAbsoluteTime, mediaPreviewEnabled, this, ::onTopLoaded, viewModel::retry)
recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
recyclerView.layoutManager = LinearLayoutManager(view.context)
layoutManager = LinearLayoutManager(view.context)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
@ -172,6 +176,17 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable {
}
}
private fun jumpToTop() {
if (isAdded) {
layoutManager?.scrollToPosition(0)
recyclerView.stopScroll()
}
}
override fun onReselect() {
jumpToTop()
}
companion object {
fun newInstance() = ConversationsFragment()
}

View file

@ -27,8 +27,6 @@ import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.NotificationsAdapter;
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder;
@ -43,6 +41,7 @@ import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.ReselectableFragment;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CollectionUtil;
import com.keylesspalace.tusky.util.Either;
@ -97,7 +96,7 @@ public class NotificationsFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener,
StatusActionListener,
NotificationsAdapter.NotificationActionListener,
Injectable {
Injectable, ReselectableFragment {
private static final String TAG = "NotificationF"; // logging tag
private static final int LOAD_AT_ONCE = 30;
@ -138,7 +137,6 @@ public class NotificationsFragment extends SFragment implements
private LinearLayoutManager layoutManager;
private EndlessOnScrollListener scrollListener;
private NotificationsAdapter adapter;
private TabLayout.OnTabSelectedListener onTabSelectedListener;
private boolean hideFab;
private boolean topLoading;
private boolean bottomLoading;
@ -250,28 +248,9 @@ public class NotificationsFragment extends SFragment implements
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
MainActivity activity = (MainActivity) getActivity();
Activity activity = getActivity();
if (activity == null) throw new AssertionError("Activity is null");
// MainActivity's layout is guaranteed to be inflated until onCreate returns.
TabLayout layout = activity.findViewById(R.id.tab_layout);
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
jumpToTop();
}
};
layout.addOnTabSelectedListener(onTabSelectedListener);
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
* guaranteed to be set until then.
* Use a modified scroll listener that both loads more notificationsEnabled as it goes, and hides
@ -323,19 +302,6 @@ public class NotificationsFragment extends SFragment implements
});
}
@Override
public void onDestroyView() {
Activity activity = getActivity();
if (activity == null) {
Log.e(TAG, "Activity is null");
} else {
TabLayout tabLayout = activity.findViewById(R.id.tab_layout);
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
}
super.onDestroyView();
}
@Override
public void onRefresh() {
swipeRefreshLayout.setEnabled(true);
@ -634,9 +600,11 @@ public class NotificationsFragment extends SFragment implements
}
private void jumpToTop() {
if (isAdded()) {
layoutManager.scrollToPosition(0);
scrollListener.reset();
}
}
private void sendFetchNotificationsRequest(String fromId, String uptoId,
final FetchEnd fetchEnd, final int pos) {
@ -974,4 +942,9 @@ public class NotificationsFragment extends SFragment implements
}
}
@Override
public void onReselect() {
jumpToTop();
}
}

View file

@ -28,8 +28,6 @@ import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import io.reactivex.Observable;
import com.keylesspalace.tusky.AccountListActivity;
import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.R;
@ -49,6 +47,7 @@ import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Filter;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.ReselectableFragment;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.network.MastodonApi;
import com.keylesspalace.tusky.repository.Placeholder;
@ -66,8 +65,8 @@ import com.keylesspalace.tusky.view.BackgroundMessageView;
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.util.ArrayList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@ -94,6 +93,7 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import at.connyduck.sparkbutton.helpers.Utils;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
@ -107,7 +107,7 @@ import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvid
public class TimelineFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener,
StatusActionListener,
Injectable {
Injectable, ReselectableFragment {
private static final String TAG = "TimelineF"; // logging tag
private static final String KIND_ARG = "kind";
private static final String HASHTAG_OR_ID_ARG = "hashtag_or_id";
@ -152,7 +152,6 @@ public class TimelineFragment extends SFragment implements
private String hashtagOrId;
private LinearLayoutManager layoutManager;
private EndlessOnScrollListener scrollListener;
private TabLayout.OnTabSelectedListener onTabSelectedListener;
private boolean filterRemoveReplies;
private boolean filterRemoveReblogs;
private boolean filterRemoveRegex;
@ -436,27 +435,6 @@ public class TimelineFragment extends SFragment implements
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (jumpToTopAllowed()) {
TabLayout layout = requireActivity().findViewById(R.id.tab_layout);
if (layout != null) {
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
jumpToTop();
}
};
layout.addOnTabSelectedListener(onTabSelectedListener);
}
}
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
* guaranteed to be set until then. */
if (actionButtonPresent()) {
@ -543,17 +521,6 @@ public class TimelineFragment extends SFragment implements
}
}
@Override
public void onDestroyView() {
if (jumpToTopAllowed()) {
TabLayout tabLayout = requireActivity().findViewById(R.id.tab_layout);
if (tabLayout != null) {
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
}
}
super.onDestroyView();
}
@Override
public void onRefresh() {
swipeRefreshLayout.setEnabled(true);
@ -895,20 +862,18 @@ public class TimelineFragment extends SFragment implements
sendFetchTimelineRequest(null, null, null, FetchEnd.BOTTOM, -1);
}
private boolean jumpToTopAllowed() {
return kind != Kind.TAG && kind != Kind.FAVOURITES;
}
private boolean actionButtonPresent() {
return kind != Kind.TAG && kind != Kind.FAVOURITES &&
getActivity() instanceof ActionButtonActivity;
}
private void jumpToTop() {
if (isAdded()) {
layoutManager.scrollToPosition(0);
recyclerView.stopScroll();
scrollListener.reset();
}
}
private Call<List<Status>> getFetchCallByTimelineType(Kind kind, String tagOrId, String fromId,
String uptoId) {
@ -1330,8 +1295,7 @@ public class TimelineFragment extends SFragment implements
List<String> payload = new ArrayList<>();
payload.add(StatusBaseViewHolder.Key.KEY_CREATED);
return payload;
}
else
} else
// If items are different - update a whole view holder
return null;
}
@ -1362,5 +1326,8 @@ public class TimelineFragment extends SFragment implements
}
@Override
public void onReselect() {
jumpToTop();
}
}

View file

@ -0,0 +1,11 @@
package com.keylesspalace.tusky.interfaces
/**
* Created by pandasoft (joelpyska1@gmail.com) on 04/04/2019.
*/
interface ReselectableFragment {
/**
* Call this method when tab reselected
*/
fun onReselect()
}

View file

@ -15,17 +15,25 @@
package com.keylesspalace.tusky.pager;
import android.util.SparseArray;
import android.view.ViewGroup;
import com.keylesspalace.tusky.fragment.AccountMediaFragment;
import com.keylesspalace.tusky.fragment.TimelineFragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
public class AccountPagerAdapter extends FragmentPagerAdapter {
private static final int TAB_COUNT = 4;
private String accountId;
private String[] pageTitles;
private SparseArray<Fragment> fragments = new SparseArray<>(TAB_COUNT);
public AccountPagerAdapter(FragmentManager manager, String accountId) {
super(manager);
this.accountId = accountId;
@ -58,11 +66,31 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
@Override
public int getCount() {
return 4;
return TAB_COUNT;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
Object fragment = super.instantiateItem(container, position);
if (fragment instanceof Fragment)
fragments.put(position, (Fragment) fragment);
return fragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
super.destroyItem(container, position, object);
fragments.remove(position);
}
@Override
public CharSequence getPageTitle(int position) {
return pageTitles[position];
}
@Nullable
public Fragment getFragment(int position) {
return fragments.get(position);
}
}

View file

@ -15,6 +15,8 @@
package com.keylesspalace.tusky.pager
import android.util.SparseArray
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
@ -22,6 +24,7 @@ import androidx.viewpager.widget.PagerAdapter
import com.keylesspalace.tusky.TabData
class MainPagerAdapter(val tabs: List<TabData>, manager: FragmentManager) : FragmentPagerAdapter(manager) {
private val fragments = SparseArray<Fragment>(tabs.size)
override fun getItem(position: Int): Fragment {
val tab = tabs[position]
@ -44,4 +47,17 @@ class MainPagerAdapter(val tabs: List<TabData>, manager: FragmentManager) : Frag
return PagerAdapter.POSITION_NONE
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position)
if (fragment is Fragment)
fragments.put(position, fragment)
return fragment
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
fragments.remove(position)
}
fun getFragment(position: Int): Fragment? = fragments[position]
}