From d2ca776b340c1f49a8b4e2c1665daf75f1343ab0 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Wed, 6 Nov 2019 20:17:53 +0100 Subject: [PATCH] Migrate to ViewPager2 (#1544) * migrate MainActivty to ViewPager2 * migrate AccountActivty to ViewPager2 * migrate ViewMediaActivty to ViewPager2 * migrate SearchActivity to ViewPager2 * checkin missing AccountPagerAdapter file * remove unused class ImageViewPager * replace SparseArray with MutableList --- app/build.gradle | 1 + .../keylesspalace/tusky/AccountActivity.kt | 23 ++-- .../com/keylesspalace/tusky/MainActivity.java | 17 ++- .../keylesspalace/tusky/ViewMediaActivity.kt | 32 +++-- .../tusky/components/search/SearchActivity.kt | 18 ++- .../search/adapter/SearchPagerAdapter.kt | 22 +--- .../tusky/fragment/AccountMediaFragment.kt | 11 +- .../tusky/fragment/ViewMediaFragment.kt | 5 +- .../tusky/pager/AccountPagerAdapter.java | 120 ------------------ .../tusky/pager/AccountPagerAdapter.kt | 65 ++++++++++ .../tusky/pager/AvatarImagePagerAdapter.kt | 17 ++- .../tusky/pager/ImagePagerAdapter.kt | 41 +++--- .../tusky/pager/MainPagerAdapter.kt | 46 ++----- .../tusky/view/ImageViewPager.java | 45 ------- .../res/drawable/tab_page_margin_black.xml | 5 - .../res/drawable/tab_page_margin_dark.xml | 5 - .../res/drawable/tab_page_margin_light.xml | 5 - .../res/layout-sw640dp/fragment_timeline.xml | 14 +- .../fragment_timeline_notifications.xml | 5 +- .../layout-sw640dp/fragment_view_thread.xml | 3 +- app/src/main/res/layout/activity_account.xml | 3 +- app/src/main/res/layout/activity_main.xml | 3 +- app/src/main/res/layout/activity_search.xml | 2 +- .../main/res/layout/activity_view_media.xml | 2 +- app/src/main/res/layout/fragment_search.xml | 20 +-- app/src/main/res/layout/fragment_timeline.xml | 19 +-- .../fragment_timeline_notifications.xml | 3 +- .../main/res/layout/fragment_view_thread.xml | 2 + app/src/main/res/values-night/styles.xml | 2 +- app/src/main/res/values/attrs.xml | 2 +- app/src/main/res/values/styles.xml | 4 +- 31 files changed, 213 insertions(+), 349 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.kt delete mode 100644 app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java delete mode 100644 app/src/main/res/drawable/tab_page_margin_black.xml delete mode 100644 app/src/main/res/drawable/tab_page_margin_dark.xml delete mode 100644 app/src/main/res/drawable/tab_page_margin_light.xml diff --git a/app/build.gradle b/app/build.gradle index 4d422dda1..4abeb0156 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -114,6 +114,7 @@ dependencies { implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.sharetarget:sharetarget:1.0.0-beta01' + implementation "androidx.viewpager2:viewpager2:1.0.0-rc01" implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 9819efcb0..5e15b1101 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -37,6 +37,7 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager +import androidx.viewpager2.widget.MarginPageTransformer import com.bumptech.glide.Glide import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.CollapsingToolbarLayout @@ -45,6 +46,7 @@ import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator import com.keylesspalace.tusky.adapter.AccountFieldAdapter import com.keylesspalace.tusky.components.report.ReportActivity import com.keylesspalace.tusky.di.ViewModelFactory @@ -187,16 +189,21 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI */ private fun setupTabs() { // Setup the tabs and timeline pager. - adapter = AccountPagerAdapter(supportFragmentManager, viewModel.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) - 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) - accountFragmentViewPager.setPageMarginDrawable(pageMarginDrawable) + adapter = AccountPagerAdapter(this, viewModel.accountId) + accountFragmentViewPager.adapter = adapter accountFragmentViewPager.offscreenPageLimit = 2 - accountTabLayout.setupWithViewPager(accountFragmentViewPager) + + 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)) + + TabLayoutMediator(accountTabLayout, accountFragmentViewPager) { + tab, position -> + tab.text = pageTitles[position] + }.attach() + + val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin) + accountFragmentViewPager.setPageTransformer(MarginPageTransformer(pageMargin)) + accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabReselected(tab: TabLayout.Tab?) { tab?.position?.let { position -> diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index b4fe52434..341a9a03b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -33,11 +33,13 @@ import androidx.emoji.text.EmojiCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; import androidx.preference.PreferenceManager; -import androidx.viewpager.widget.ViewPager; +import androidx.viewpager2.widget.MarginPageTransformer; +import androidx.viewpager2.widget.ViewPager2; import com.bumptech.glide.Glide; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import com.keylesspalace.tusky.appstore.CacheUpdater; import com.keylesspalace.tusky.appstore.EventHub; import com.keylesspalace.tusky.appstore.MainTabsChangedEvent; @@ -53,7 +55,6 @@ import com.keylesspalace.tusky.pager.MainPagerAdapter; import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.NotificationHelper; import com.keylesspalace.tusky.util.ShareShortcutHelper; -import com.keylesspalace.tusky.util.ThemeUtils; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.materialdrawer.AccountHeader; import com.mikepenz.materialdrawer.AccountHeaderBuilder; @@ -114,7 +115,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut private AccountHeader headerResult; private Drawer drawer; private TabLayout tabLayout; - private ViewPager viewPager; + private ViewPager2 viewPager; private int notificationTabPosition; private MainPagerAdapter adapter; @@ -207,10 +208,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut setupTabs(showNotificationTab); int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin); - viewPager.setPageMargin(pageMargin); - Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable, - R.drawable.tab_page_margin_dark); - viewPager.setPageMarginDrawable(pageMarginDrawable); + viewPager.setPageTransformer(new MarginPageTransformer(pageMargin)); tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override @@ -467,10 +465,11 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut private void setupTabs(boolean selectNotificationTab) { List tabs = accountManager.getActiveAccount().getTabPreferences(); - adapter = new MainPagerAdapter(tabs, getSupportFragmentManager()); + adapter = new MainPagerAdapter(tabs, this); viewPager.setAdapter(adapter); - tabLayout.setupWithViewPager(viewPager); + new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { }).attach(); + tabLayout.removeAllTabs(); for (int i = 0; i < tabs.size(); i++) { TabLayout.Tab tab = tabLayout.newTab() diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index 966ee117b..66bf8e8df 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -37,9 +37,10 @@ import android.view.View import android.webkit.MimeTypeMap import android.widget.Toast import androidx.core.content.FileProvider +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.Lifecycle -import androidx.viewpager.widget.PagerAdapter -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 import com.bumptech.glide.Glide import com.bumptech.glide.request.FutureTarget import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID @@ -110,23 +111,23 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener // Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener // but it cannot be expressed and if I don't specify type explicitly compilation fails // (probably a bug in compiler) - val adapter: PagerAdapter = if (attachments != null) { + val adapter: ViewMediaAdapter = if (attachments != null) { val realAttachs = attachments!!.map(AttachmentViewData::attachment) // Setup the view pager. - ImagePagerAdapter(supportFragmentManager, realAttachs, initialPosition) + ImagePagerAdapter(this, realAttachs, initialPosition) } else { val avatarUrl = intent.getStringExtra(EXTRA_AVATAR_URL) ?: throw IllegalArgumentException("attachment list or avatar url has to be set") - AvatarImagePagerAdapter(supportFragmentManager, avatarUrl) + AvatarImagePagerAdapter(this, avatarUrl) } viewPager.adapter = adapter - viewPager.currentItem = initialPosition - viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { + viewPager.setCurrentItem(initialPosition, false) + viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { - toolbar.title = adapter.getPageTitle(position) + toolbar.title = getPageTitle(position) } }) @@ -136,7 +137,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true) actionBar.setDisplayShowHomeEnabled(true) - actionBar.title = adapter.getPageTitle(initialPosition) + actionBar.title = getPageTitle(initialPosition) } toolbar.setNavigationOnClickListener { supportFinishAfterTransition() } toolbar.setOnMenuItemClickListener { item: MenuItem -> @@ -153,7 +154,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener window.statusBarColor = Color.BLACK window.sharedElementEnterTransition.addListener(object : NoopTransitionListener { override fun onTransitionEnd(transition: Transition) { - (adapter as SharedElementTransitionListener).onTransitionEnd() + adapter.onTransitionEnd(viewPager.currentItem) window.sharedElementEnterTransition.removeListener(this) } }) @@ -198,6 +199,13 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener .start() } + private fun getPageTitle(position: Int): CharSequence { + if(attachments == null) { + return "" + } + return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size) + } + private fun downloadMedia() { val url = attachments!![viewPager.currentItem].attachment.url val filename = Uri.parse(url).lastPathSegment @@ -323,8 +331,8 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener } } -interface SharedElementTransitionListener { - fun onTransitionEnd() +abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) { + abstract fun onTransitionEnd(position: Int) } interface NoopTransitionListener : Transition.TransitionListener { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt index e7ae58017..54becb406 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt @@ -23,6 +23,7 @@ import android.view.Menu import android.view.MenuItem import androidx.appcompat.widget.SearchView import androidx.lifecycle.ViewModelProviders +import com.google.android.material.tabs.TabLayoutMediator import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter @@ -57,8 +58,12 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha } private fun setupPages() { - pages.adapter = SearchPagerAdapter(this, supportFragmentManager) - tabs.setupWithViewPager(pages) + pages.adapter = SearchPagerAdapter(this) + + TabLayoutMediator(tabs, pages) { + tab, position -> + tab.text = getPageTitle(position) + }.attach() } override fun onNewIntent(intent: Intent) { @@ -99,6 +104,15 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha return false } + private fun getPageTitle(position: Int): CharSequence? { + return when (position) { + 0 -> getString(R.string.title_statuses) + 1 -> getString(R.string.title_accounts) + 2 -> getString(R.string.title_hashtags_dialog) + else -> throw IllegalArgumentException("Unknown page index: $position") + } + } + private fun handleIntent(intent: Intent) { if (Intent.ACTION_SEARCH == intent.action) { viewModel.currentQuery = intent.getStringExtra(SearchManager.QUERY) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt index d78d85bd2..845abaf89 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt @@ -15,18 +15,16 @@ package com.keylesspalace.tusky.components.search.adapter -import android.content.Context import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter -import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.search.SearchType +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragment import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment -class SearchPagerAdapter(private val context: Context, manager: FragmentManager) : FragmentPagerAdapter(manager) { - override fun getItem(position: Int): Fragment { +class SearchPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { + + override fun createFragment(position: Int): Fragment { return when (position) { 0 -> SearchStatusesFragment.newInstance() 1 -> SearchAccountsFragment.newInstance() @@ -35,14 +33,6 @@ class SearchPagerAdapter(private val context: Context, manager: FragmentManager) } } - override fun getPageTitle(position: Int): CharSequence? { - return when (position) { - 0 -> context.getString(R.string.title_statuses) - 1 -> context.getString(R.string.title_accounts) - 2 -> context.getString(R.string.title_hashtags_dialog) - else -> throw IllegalArgumentException("Unknown page index: $position") - } - } + override fun getItemCount() = 3 - override fun getCount(): Int = 3 } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index 3415b3400..06073d86d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -81,7 +81,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { private var currentCall: Call>? = null private val statuses = mutableListOf() private var fetchingStatus = FetchingStatus.NOT_FETCHING - private var isVisibleToUser: Boolean = false private lateinit var accountId: String @@ -216,7 +215,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } }) - if (isVisibleToUser) doInitialLoadingIfNeeded() + doInitialLoadingIfNeeded() } private fun refresh() { @@ -235,14 +234,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { topProgressBar?.show() } - // That's sort of an optimization to only load media once user has opened the tab - // Attention: can be called before *any* lifecycle method! - override fun setUserVisibleHint(isVisibleToUser: Boolean) { - super.setUserVisibleHint(isVisibleToUser) - this.isVisibleToUser = isVisibleToUser - if (isVisibleToUser && isAdded) doInitialLoadingIfNeeded() - } - private fun doInitialLoadingIfNeeded() { if (isAdded) { statusView.hide() diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt index 540469fac..5a50fd18c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt @@ -18,13 +18,12 @@ package com.keylesspalace.tusky.fragment import android.os.Bundle import android.text.TextUtils import android.widget.TextView -import com.keylesspalace.tusky.SharedElementTransitionListener import com.keylesspalace.tusky.ViewMediaActivity import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.util.visible -abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListener { +abstract class ViewMediaFragment : BaseFragment() { private var toolbarVisibiltyDisposable: Function0? = null abstract fun setupMediaView(url: String, previewUrl: String?) @@ -71,6 +70,8 @@ abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListen } } + abstract fun onTransitionEnd() + protected fun finalizeViewSetup(url: String, previewUrl: String?, description: String?) { val mediaActivity = activity as ViewMediaActivity setupMediaView(url, previewUrl) diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java b/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java deleted file mode 100644 index de2f05234..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java +++ /dev/null @@ -1,120 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -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 com.keylesspalace.tusky.interfaces.RefreshableFragment; - -import java.util.HashSet; -import java.util.Set; - -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 fragments = new SparseArray<>(TAB_COUNT); - - private final Set pagesToRefresh = new HashSet<>(); - - public AccountPagerAdapter(FragmentManager manager, String accountId) { - super(manager); - this.accountId = accountId; - } - - public void setPageTitles(String[] titles) { - pageTitles = titles; - } - - @NonNull - @Override - public Fragment getItem(int position) { - switch (position) { - case 0: { - return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId,false); - } - case 1: { - return TimelineFragment.newInstance(TimelineFragment.Kind.USER_WITH_REPLIES, accountId,false); - } - case 2: { - return TimelineFragment.newInstance(TimelineFragment.Kind.USER_PINNED, accountId,false); - } - case 3: { - return AccountMediaFragment.newInstance(accountId,false); - } - default: { - throw new AssertionError("Page " + position + " is out of AccountPagerAdapter bounds"); - } - } - } - - @Override - public int getCount() { - 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); - if (pagesToRefresh.contains(position)) { - if (fragment instanceof RefreshableFragment) - ((RefreshableFragment) fragment).refreshContent(); - pagesToRefresh.remove(position); - } - 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); - } - - public void refreshContent(){ - for (int i=0;i. */ + +package com.keylesspalace.tusky.pager + +import androidx.fragment.app.* + +import com.keylesspalace.tusky.fragment.AccountMediaFragment +import com.keylesspalace.tusky.fragment.TimelineFragment +import com.keylesspalace.tusky.interfaces.RefreshableFragment + +import androidx.viewpager2.adapter.FragmentStateAdapter +import java.lang.ref.WeakReference + +class AccountPagerAdapter( + activity: FragmentActivity, + private val accountId: String +) : FragmentStateAdapter(activity) { + + private val fragments = MutableList?>(TAB_COUNT) { null } + + override fun getItemCount() = TAB_COUNT + + override fun createFragment(position: Int): Fragment { + val fragment: Fragment = when (position) { + 0 -> TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId, false) + 1 -> TimelineFragment.newInstance(TimelineFragment.Kind.USER_WITH_REPLIES, accountId, false) + 2 -> TimelineFragment.newInstance(TimelineFragment.Kind.USER_PINNED, accountId, false) + 3 -> AccountMediaFragment.newInstance(accountId, false) + else -> throw AssertionError("Page $position is out of AccountPagerAdapter bounds") + } + + fragments[position] = WeakReference(fragment) + return fragment + } + + fun getFragment(position: Int): Fragment? { + return fragments[position]?.get() + } + + fun refreshContent() { + for (i in 0 until TAB_COUNT) { + val fragment = getFragment(i) + if (fragment != null && fragment is RefreshableFragment) { + (fragment as RefreshableFragment).refreshContent() + } + } + } + + companion object { + private const val TAB_COUNT = 4 + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt index 1beab552e..a3dfcf2ab 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt @@ -1,13 +1,16 @@ package com.keylesspalace.tusky.pager import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter -import com.keylesspalace.tusky.SharedElementTransitionListener +import androidx.fragment.app.FragmentActivity +import com.keylesspalace.tusky.ViewMediaAdapter import com.keylesspalace.tusky.fragment.ViewMediaFragment -class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avatarUrl: String) : FragmentPagerAdapter(fragmentManager), SharedElementTransitionListener { - override fun getItem(position: Int): Fragment { +class AvatarImagePagerAdapter( + activity: FragmentActivity, + private val avatarUrl: String +) : ViewMediaAdapter(activity) { + + override fun createFragment(position: Int): Fragment { return if (position == 0) { ViewMediaFragment.newAvatarInstance(avatarUrl) } else { @@ -15,8 +18,8 @@ class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avat } } - override fun getCount() = 1 + override fun getItemCount() = 1 - override fun onTransitionEnd() { + override fun onTransitionEnd(position: Int) { } } diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt index 983770d2a..4f813d8b1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt @@ -1,53 +1,42 @@ package com.keylesspalace.tusky.pager -import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter -import com.keylesspalace.tusky.SharedElementTransitionListener +import androidx.fragment.app.FragmentActivity +import com.keylesspalace.tusky.ViewMediaAdapter import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.fragment.ViewMediaFragment -import java.util.* +import java.lang.ref.WeakReference class ImagePagerAdapter( - fragmentManager: FragmentManager, + activity: FragmentActivity, private val attachments: List, private val initialPosition: Int -) : FragmentStatePagerAdapter(fragmentManager), SharedElementTransitionListener { +) : ViewMediaAdapter(activity) { - private var primaryItem: ViewMediaFragment? = null private var didTransition = false + private val fragments = MutableList?>(attachments.size) { null } - override fun setPrimaryItem(container: ViewGroup, position: Int, item: Any) { - super.setPrimaryItem(container, position, item) - this.primaryItem = item as ViewMediaFragment - } + override fun getItemCount() = attachments.size - override fun getItem(position: Int): Fragment { - return if (position >= 0 && position < attachments.size) { + override fun createFragment(position: Int): Fragment { + if (position >= 0 && position < attachments.size) { // Fragment should not wait for or start transition if it already happened but we // instantiate the same fragment again, e.g. open the first photo, scroll to the - // forth photo and then back to the first. The first fragment will trz to start the + // forth photo and then back to the first. The first fragment will try to start the // transition and wait until it's over and it will never take place. - ViewMediaFragment.newInstance( + val fragment = ViewMediaFragment.newInstance( attachment = attachments[position], shouldStartPostponedTransition = !didTransition && position == initialPosition ) + fragments[position] = WeakReference(fragment) + return fragment } else { throw IllegalStateException() } } - override fun getCount(): Int { - return attachments.size - } - - override fun getPageTitle(position: Int): CharSequence { - return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments.size) - } - - override fun onTransitionEnd() { + override fun onTransitionEnd(position: Int) { this.didTransition = true - primaryItem?.onTransitionEnd() + fragments[position]?.get()?.onTransitionEnd() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt index 7f0558c35..58728340e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt @@ -15,49 +15,23 @@ 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 -import androidx.viewpager.widget.PagerAdapter +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter import com.keylesspalace.tusky.TabData +import java.lang.ref.WeakReference -class MainPagerAdapter(val tabs: List, manager: FragmentManager) : FragmentPagerAdapter(manager) { - private val fragments = SparseArray(tabs.size) +class MainPagerAdapter(val tabs: List, activity: FragmentActivity) : FragmentStateAdapter(activity) { + private val fragments = MutableList?>(tabs.size) { null } - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { val tab = tabs[position] - return tab.fragment(tab.arguments) - } - - override fun getCount(): Int { - return tabs.size - } - - override fun getPageTitle(position: Int): CharSequence? { - return null - } - - override fun getItemId(position: Int): Long { - return tabs[position].hashCode() + position.toLong() - } - - override fun getItemPosition(item: Any): Int { - 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) + val fragment = tab.fragment(tab.arguments) + fragments[position] = WeakReference(fragment) return fragment } - override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { - super.destroyItem(container, position, `object`) - fragments.remove(position) - } + override fun getItemCount() = tabs.size - fun getFragment(position: Int): Fragment? = fragments[position] + fun getFragment(position: Int): Fragment? = fragments[position]?.get() } diff --git a/app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java b/app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java deleted file mode 100644 index 22e967afb..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.view; - -import android.content.Context; -import androidx.viewpager.widget.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; - -/** - * This class is entirely to address a known issue with com.github.chrisbanes.photoview.PhotoView. - * ViewPager will throw exceptions when a PhotoView is placed within it, so this subclass eats those - * exceptions. - */ -public class ImageViewPager extends ViewPager { - public ImageViewPager(Context context) { - super(context); - } - - public ImageViewPager(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - try { - return super.onInterceptTouchEvent(ev); - } catch (IllegalArgumentException e) { - return false; - } - } -} diff --git a/app/src/main/res/drawable/tab_page_margin_black.xml b/app/src/main/res/drawable/tab_page_margin_black.xml deleted file mode 100644 index a9d6d6cfa..000000000 --- a/app/src/main/res/drawable/tab_page_margin_black.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_page_margin_dark.xml b/app/src/main/res/drawable/tab_page_margin_dark.xml deleted file mode 100644 index 3bd2e0c2a..000000000 --- a/app/src/main/res/drawable/tab_page_margin_dark.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_page_margin_light.xml b/app/src/main/res/drawable/tab_page_margin_light.xml deleted file mode 100644 index bc46cd5f9..000000000 --- a/app/src/main/res/drawable/tab_page_margin_light.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_timeline.xml b/app/src/main/res/layout-sw640dp/fragment_timeline.xml index 27e983e5c..56150457d 100644 --- a/app/src/main/res/layout-sw640dp/fragment_timeline.xml +++ b/app/src/main/res/layout-sw640dp/fragment_timeline.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/tab_page_margin_drawable"> + android:layout_height="match_parent"> + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml b/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml index e0d0583a5..82a13db74 100644 --- a/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml +++ b/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/tab_page_margin_drawable"> + android:layout_height="match_parent"> \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml index bbe5fd807..b8e1cf9a9 100644 --- a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml +++ b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml @@ -1,8 +1,7 @@ + android:layout_height="match_parent"> - - - - diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 4381e5993..a4ea14020 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -4,20 +4,20 @@ android:id="@+id/layoutRoot" android:layout_width="@dimen/timeline_width" android:layout_height="match_parent" - android:background="?attr/tab_page_margin_drawable"> + android:background="?attr/tab_page_margin_color"> + android:id="@+id/swipeRefreshLayout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:id="@+id/searchRecyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:background="?attr/window_background" + tools:listitem="@layout/item_account" /> diff --git a/app/src/main/res/layout/fragment_timeline.xml b/app/src/main/res/layout/fragment_timeline.xml index e246ed6fd..94431fffe 100644 --- a/app/src/main/res/layout/fragment_timeline.xml +++ b/app/src/main/res/layout/fragment_timeline.xml @@ -3,7 +3,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="?attr/window_background"> + + tools:visibility="visible" /> + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timeline_notifications.xml b/app/src/main/res/layout/fragment_timeline_notifications.xml index 6f7dfe1d2..d27c6183a 100644 --- a/app/src/main/res/layout/fragment_timeline_notifications.xml +++ b/app/src/main/res/layout/fragment_timeline_notifications.xml @@ -58,7 +58,8 @@ + android:layout_height="match_parent" + android:background="?attr/window_background" /> diff --git a/app/src/main/res/layout/fragment_view_thread.xml b/app/src/main/res/layout/fragment_view_thread.xml index aa6877d41..a990942e3 100644 --- a/app/src/main/res/layout/fragment_view_thread.xml +++ b/app/src/main/res/layout/fragment_view_thread.xml @@ -9,5 +9,7 @@ android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="?attr/window_background" android:scrollbars="vertical" /> + diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 3e3a12ae9..643a42066 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -41,7 +41,7 @@ @drawable/status_divider_dark @drawable/conversation_thread_line_dark @color/tusky_blue - @drawable/tab_page_margin_dark + @color/tab_page_margin_dark @color/color_background_dark @color/toolbar_icon_dark @color/account_toolbar_icon_collapsed_dark diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 29dbcdeba..46395bb91 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -25,7 +25,7 @@ - + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 581c5b149..5eb6c78b0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -96,7 +96,7 @@ @drawable/conversation_thread_line_light @color/tusky_blue - @drawable/tab_page_margin_light + @color/tab_page_margin_light @color/color_primary_dark_light @color/toolbar_icon_dark @@ -200,7 +200,7 @@ @color/color_primary_black @color/text_color_primary_black @color/text_color_primary_black - @drawable/tab_page_margin_black + @color/tab_page_margin_black