Merge pull request #13093 from nextcloud/view-pager-2-implementation

Migrate to View Pager 2 & Crash-Fix | Farewell to ViewPager 1 🥳
This commit is contained in:
Alper Öztürk 2024-06-12 14:33:54 +02:00 committed by GitHub
commit 5d83a2961b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 96 additions and 97 deletions

View file

@ -18,7 +18,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.appinfo.AppInfo
@ -39,7 +39,7 @@ import javax.inject.Inject
/**
* Activity displaying general feature after a fresh install.
*/
class FirstRunActivity : BaseActivity(), ViewPager.OnPageChangeListener, Injectable {
class FirstRunActivity : BaseActivity(), Injectable {
@JvmField
@Inject
@ -171,10 +171,14 @@ class FirstRunActivity : BaseActivity(), ViewPager.OnPageChangeListener, Injecta
@Suppress("SpreadOperator")
private fun setupFeaturesViewAdapter() {
val featuresViewAdapter = FeaturesViewAdapter(supportFragmentManager, *firstRun)
binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.count)
val featuresViewAdapter = FeaturesViewAdapter(this, *firstRun)
binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.itemCount)
binding.contentPanel.adapter = featuresViewAdapter
binding.contentPanel.addOnPageChangeListener(this)
binding.contentPanel.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
binding.progressIndicator.animateToStep(position + 1)
}
})
}
private fun handleOnBackPressed() {
@ -236,18 +240,6 @@ class FirstRunActivity : BaseActivity(), ViewPager.OnPageChangeListener, Injecta
super.onStop()
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
// unused but to be implemented due to abstract parent
}
override fun onPageSelected(position: Int) {
binding.progressIndicator.animateToStep(position + 1)
}
override fun onPageScrollStateChanged(state: Int) {
// unused but to be implemented due to abstract parent
}
companion object {
const val EXTRA_ALLOW_CLOSE = "ALLOW_CLOSE"
const val EXTRA_EXIT = "EXIT"

View file

@ -13,7 +13,7 @@ import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.FragmentActivity
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.appinfo.AppInfo
import com.nextcloud.client.di.Injectable
@ -29,7 +29,7 @@ import javax.inject.Inject
/**
* Activity displaying new features after an update.
*/
class WhatsNewActivity : FragmentActivity(), ViewPager.OnPageChangeListener, Injectable {
class WhatsNewActivity : FragmentActivity(), Injectable {
@JvmField
@Inject
@ -64,7 +64,11 @@ class WhatsNewActivity : FragmentActivity(), ViewPager.OnPageChangeListener, Inj
val showWebView = urls.isNotEmpty()
setupFeatureViewAdapter(showWebView, urls)
binding.contentPanel.addOnPageChangeListener(this)
binding.contentPanel.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
controlPanelOnPageSelected(position)
}
})
setupForwardImageButton()
setupSkipImageButton()
setupWelcomeText(showWebView)
@ -75,15 +79,15 @@ class WhatsNewActivity : FragmentActivity(), ViewPager.OnPageChangeListener, Inj
@Suppress("SpreadOperator")
private fun setupFeatureViewAdapter(showWebView: Boolean, urls: Array<String>) {
val adapter = if (showWebView) {
FeaturesWebViewAdapter(supportFragmentManager, *urls)
FeaturesWebViewAdapter(this, *urls)
} else {
onboarding?.let {
FeaturesViewAdapter(supportFragmentManager, *it.whatsNew)
FeaturesViewAdapter(this, *it.whatsNew)
}
}
adapter?.let {
binding.progressIndicator.setNumberOfSteps(it.count)
binding.progressIndicator.setNumberOfSteps(it.itemCount)
binding.contentPanel.adapter = it
}
}
@ -142,14 +146,8 @@ class WhatsNewActivity : FragmentActivity(), ViewPager.OnPageChangeListener, Inj
preferences?.lastSeenVersionCode = BuildConfig.VERSION_CODE
}
override fun onPageSelected(position: Int) {
private fun controlPanelOnPageSelected(position: Int) {
binding.progressIndicator.animateToStep(position + 1)
updateNextButtonIfNeeded()
}
@Suppress("EmptyFunctionBlock")
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
@Suppress("EmptyFunctionBlock")
override fun onPageScrollStateChanged(state: Int) {}
}

View file

@ -9,26 +9,28 @@ package com.owncloud.android.ui.adapter;
import com.owncloud.android.features.FeatureItem;
import com.owncloud.android.ui.fragment.FeatureFragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
public class FeaturesViewAdapter extends FragmentPagerAdapter {
public class FeaturesViewAdapter extends FragmentStateAdapter {
private FeatureItem[] mFeatures;
private final FeatureItem[] mFeatures;
public FeaturesViewAdapter(FragmentManager fm, FeatureItem... features) {
super(fm);
public FeaturesViewAdapter(FragmentActivity fragmentActivity, FeatureItem... features) {
super(fragmentActivity);
mFeatures = features;
}
@NonNull
@Override
public Fragment getItem(int position) {
public Fragment createFragment(int position) {
return FeatureFragment.newInstance(mFeatures[position]);
}
@Override
public int getCount() {
public int getItemCount() {
return mFeatures.length;
}
}

View file

@ -8,25 +8,27 @@ package com.owncloud.android.ui.adapter;
import com.owncloud.android.ui.fragment.FeatureWebFragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
public class FeaturesWebViewAdapter extends FragmentPagerAdapter {
public class FeaturesWebViewAdapter extends FragmentStateAdapter {
private String[] mWebUrls;
public FeaturesWebViewAdapter(FragmentManager fm, String... webUrls) {
super(fm);
public FeaturesWebViewAdapter(FragmentActivity fragmentActivity, String... webUrls) {
super(fragmentActivity);
mWebUrls = webUrls;
}
@NonNull
@Override
public Fragment getItem(int position) {
public Fragment createFragment(int position) {
return FeatureWebFragment.newInstance(mWebUrls[position]);
}
@Override
public int getCount() {
public int getItemCount() {
return mWebUrls.length;
}
}

View file

@ -15,13 +15,13 @@ import com.owncloud.android.utils.MimeTypeUtil;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
/**
* File details pager adapter.
*/
public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
public class FileDetailTabAdapter extends FragmentStateAdapter {
private final OCFile file;
private final User user;
private final boolean showSharingTab;
@ -30,33 +30,16 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
private FileDetailActivitiesFragment fileDetailActivitiesFragment;
private ImageDetailFragment imageDetailFragment;
public FileDetailTabAdapter(FragmentManager fm,
public FileDetailTabAdapter(FragmentActivity fragmentActivity,
OCFile file,
User user,
boolean showSharingTab) {
super(fm);
super(fragmentActivity);
this.file = file;
this.user = user;
this.showSharingTab = showSharingTab;
}
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
default:
fileDetailActivitiesFragment = FileDetailActivitiesFragment.newInstance(file, user);
return fileDetailActivitiesFragment;
case 1:
fileDetailSharingFragment = FileDetailSharingFragment.newInstance(file, user);
return fileDetailSharingFragment;
case 2:
imageDetailFragment = ImageDetailFragment.newInstance(file, user);
return imageDetailFragment;
}
}
public FileDetailSharingFragment getFileDetailSharingFragment() {
return fileDetailSharingFragment;
}
@ -69,8 +52,27 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
return imageDetailFragment;
}
@NonNull
@Override
public int getCount() {
public Fragment createFragment(int position) {
return switch (position) {
default -> {
fileDetailActivitiesFragment = FileDetailActivitiesFragment.newInstance(file, user);
yield fileDetailActivitiesFragment;
}
case 1 -> {
fileDetailSharingFragment = FileDetailSharingFragment.newInstance(file, user);
yield fileDetailSharingFragment;
}
case 2 -> {
imageDetailFragment = ImageDetailFragment.newInstance(file, user);
yield imageDetailFragment;
}
};
}
@Override
public int getItemCount() {
if (showSharingTab) {
if (MimeTypeUtil.isImage(file)) {
return 3;

View file

@ -75,6 +75,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager2.widget.ViewPager2;
/**
* This Fragment is used to display the details about a file.
@ -166,7 +167,12 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
if (binding == null) {
return null;
}
return ((FileDetailTabAdapter) binding.pager.getAdapter()).getFileDetailSharingFragment();
if (binding.pager.getAdapter() instanceof FileDetailTabAdapter adapter) {
return adapter.getFileDetailSharingFragment();
}
return null;
}
/**
@ -175,7 +181,11 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
* @return reference to the {@link FileDetailActivitiesFragment}
*/
public FileDetailActivitiesFragment getFileDetailActivitiesFragment() {
return ((FileDetailTabAdapter) binding.pager.getAdapter()).getFileDetailActivitiesFragment();
if (binding.pager.getAdapter() instanceof FileDetailTabAdapter adapter) {
return adapter.getFileDetailActivitiesFragment();
}
return null;
}
public void goBackToOCFileListFragment() {
@ -296,12 +306,13 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
viewThemeUtils.material.themeTabLayout(binding.tabLayout);
final FileDetailTabAdapter adapter = new FileDetailTabAdapter(getFragmentManager(),
final FileDetailTabAdapter adapter = new FileDetailTabAdapter(requireActivity(),
getFile(),
user,
showSharingTab());
binding.pager.setAdapter(adapter);
binding.pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabLayout) {
binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
final FileDetailActivitiesFragment fragment = getFileDetailActivitiesFragment();
@ -334,10 +345,11 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
}
});
TabLayout.Tab tab = binding.tabLayout.getTabAt(activeTab);
if (tab != null) {
tab.select();
}
binding.tabLayout.post(() -> {
TabLayout.Tab tab1 = binding.tabLayout.getTabAt(activeTab);
if (tab1 == null) return;
tab1.select();
});
}
@Override

View file

@ -79,9 +79,7 @@ import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import pl.droidsonroids.gif.GifDrawable;
@ -128,7 +126,7 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
* This method hides to client objects the need of doing the construction in two steps.
*
* @param imageFile An {@link OCFile} to preview as an image in the fragment
* @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStateAdapter} ;
* @param ignoreFirstSavedState Flag to work around an unexpected behaviour of { FragmentStateAdapter } ;
* TODO better solution
*/
public static PreviewImageFragment newInstance(@NonNull OCFile imageFile,

View file

@ -135,10 +135,6 @@ class PreviewImagePagerAdapter : FragmentStateAdapter {
}
}
override fun getItemId(position: Int): Long {
return imageFiles[position].hashCode().toLong()
}
private fun addVideoOfLivePhoto(file: OCFile) {
file.livePhotoVideo = selectedFile
}

View file

@ -185,7 +185,7 @@
app:tabTextColor="@color/text_color"
app:tabInlineLabel="true" />
<androidx.viewpager.widget.ViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -20,7 +20,7 @@
android:layout_marginBottom="@dimen/standard_margin"
android:layout_marginTop="@dimen/standard_margin">
<androidx.viewpager.widget.ViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/contentPanel"
android:layout_width="match_parent"
android:layout_height="0dp"

View file

@ -24,7 +24,7 @@
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/primary_button_text_color"/>
<androidx.viewpager.widget.ViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/contentPanel"
android:layout_width="match_parent"
android:layout_height="0dp"

View file

@ -5817,17 +5817,14 @@
<sha256 value="66afb9f2eea39427f6f03c14c5b82ca240157e22b8b2a764f0a7c8ad87cb2d3e" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library"
version="cda1b08aa81b74201177f29c2326abee62f32c15">
<artifact name="android-library-cda1b08aa81b74201177f29c2326abee62f32c15.aar">
<sha256 value="39c76af292201a94cf0753f296a18deb5512d88e97537f7c4e9a766ec50c1520"
origin="Generated by Gradle" reason="Artifact is not signed" />
</artifact>
<artifact name="android-library-cda1b08aa81b74201177f29c2326abee62f32c15.module">
<sha256 value="c7686ef2125d141196bb3e1937a12f0647e5300accc132ddc11dfe37f9db66f2"
origin="Generated by Gradle" reason="Artifact is not signed" />
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="cda1b08aa81b74201177f29c2326abee62f32c15">
<artifact name="android-library-cda1b08aa81b74201177f29c2326abee62f32c15.aar">
<sha256 value="39c76af292201a94cf0753f296a18deb5512d88e97537f7c4e9a766ec50c1520" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-cda1b08aa81b74201177f29c2326abee62f32c15.module">
<sha256 value="c7686ef2125d141196bb3e1937a12f0647e5300accc132ddc11dfe37f9db66f2" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="e7a13d03c1e7549a301edb8b4b58d1c5dda84123">
<artifact name="android-library-e7a13d03c1e7549a301edb8b4b58d1c5dda84123.aar">
<sha256 value="57ab4fd7c922875a7e0b5feac20aa27ab5df0fd3b4e042f92ed727c0b6316e81" origin="Generated by Gradle" reason="Artifact is not signed"/>