From 74542e5c586ed4f7b0c31f918f4c7a4c511d055c Mon Sep 17 00:00:00 2001 From: kadirkid Date: Sun, 21 Apr 2024 22:22:01 +0400 Subject: [PATCH 1/2] Add support for dual pages on foldable devices --- app/build.gradle | 1 + .../di/module/activity/PagerActivityModule.kt | 2 +- .../labs/androidquran/ui/PagerActivity.kt | 176 ++++++++++++------ .../labs/androidquran/util/QuranUtils.java | 6 +- gradle/libs.versions.toml | 3 +- 5 files changed, 127 insertions(+), 61 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2c99a2e8c7..75275afd4d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -156,6 +156,7 @@ dependencies { implementation libs.androidx.recyclerview implementation libs.material implementation libs.androidx.swiperefreshlayout + implementation libs.androidx.window // compose implementation libs.compose.ui diff --git a/app/src/main/java/com/quran/labs/androidquran/di/module/activity/PagerActivityModule.kt b/app/src/main/java/com/quran/labs/androidquran/di/module/activity/PagerActivityModule.kt index ba29449085..82b4f52726 100644 --- a/app/src/main/java/com/quran/labs/androidquran/di/module/activity/PagerActivityModule.kt +++ b/app/src/main/java/com/quran/labs/androidquran/di/module/activity/PagerActivityModule.kt @@ -33,7 +33,7 @@ object PagerActivityModule { @Provides @ActivityScope fun provideImageWidth(@ActivityContext context: Context, screenInfo: QuranScreenInfo): String { - return if (QuranUtils.isDualPages(context, screenInfo)) { + return if (QuranUtils.isDualPages(context, screenInfo, false)) { screenInfo.tabletWidthParam } else { screenInfo.widthParam diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt index cbec1e8b1e..df552c2c5f 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt +++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt @@ -33,10 +33,15 @@ import androidx.appcompat.widget.Toolbar import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewpager.widget.NonRestoringViewPager import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager.OnPageChangeListener import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowInfoTracker import com.quran.data.core.QuranInfo import com.quran.data.dao.BookmarksDao import com.quran.data.model.QuranText @@ -133,8 +138,10 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn @@ -168,6 +175,7 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat private var promptedForExtraDownload = false private var progressDialog: ProgressDialog? = null private var isInMultiWindowMode = false + private var isFoldableDeviceOpenAndVertical = false private var bookmarksMenuItem: MenuItem? = null @@ -196,29 +204,52 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat private var lastSelectedTranslationAyah: QuranAyahInfo? = null private var lastActivatedLocalTranslations: Array = emptyArray() - @Inject lateinit var bookmarksDao: BookmarksDao - @Inject lateinit var recentPagePresenter: RecentPagePresenter - @Inject lateinit var quranSettings: QuranSettings - @Inject lateinit var quranScreenInfo: QuranScreenInfo - @Inject lateinit var arabicDatabaseUtils: ArabicDatabaseUtils - @Inject lateinit var quranAppUtils: QuranAppUtils - @Inject lateinit var shareUtil: ShareUtil - @Inject lateinit var audioUtils: AudioUtils - @Inject lateinit var quranDisplayData: QuranDisplayData - @Inject lateinit var quranInfo: QuranInfo - @Inject lateinit var quranFileUtils: QuranFileUtils - @Inject lateinit var audioPresenter: AudioPresenter - @Inject lateinit var quranEventLogger: QuranEventLogger - @Inject lateinit var audioStatusRepository: AudioStatusRepository - @Inject lateinit var readingEventPresenter: ReadingEventPresenter - @Inject lateinit var pageProviderFactoryProvider: PageViewFactoryProvider - @Inject lateinit var additionalAyahPanels: Set<@JvmSuppressWildcards AyahActionFragmentProvider> - @Inject lateinit var pagerActivityRecitationPresenter: PagerActivityRecitationPresenter - @Inject lateinit var translationListPresenter: TranslationListPresenter - @Inject lateinit var audioBarEventRepository: AudioBarEventRepository - @Inject lateinit var downloadInfoStreams: DownloadInfoStreams - @Inject lateinit var qariManager: CurrentQariManager - @Inject lateinit var downloadBridge: DownloadBridge + @Inject + lateinit var bookmarksDao: BookmarksDao + @Inject + lateinit var recentPagePresenter: RecentPagePresenter + @Inject + lateinit var quranSettings: QuranSettings + @Inject + lateinit var quranScreenInfo: QuranScreenInfo + @Inject + lateinit var arabicDatabaseUtils: ArabicDatabaseUtils + @Inject + lateinit var quranAppUtils: QuranAppUtils + @Inject + lateinit var shareUtil: ShareUtil + @Inject + lateinit var audioUtils: AudioUtils + @Inject + lateinit var quranDisplayData: QuranDisplayData + @Inject + lateinit var quranInfo: QuranInfo + @Inject + lateinit var quranFileUtils: QuranFileUtils + @Inject + lateinit var audioPresenter: AudioPresenter + @Inject + lateinit var quranEventLogger: QuranEventLogger + @Inject + lateinit var audioStatusRepository: AudioStatusRepository + @Inject + lateinit var readingEventPresenter: ReadingEventPresenter + @Inject + lateinit var pageProviderFactoryProvider: PageViewFactoryProvider + @Inject + lateinit var additionalAyahPanels: Set<@JvmSuppressWildcards AyahActionFragmentProvider> + @Inject + lateinit var pagerActivityRecitationPresenter: PagerActivityRecitationPresenter + @Inject + lateinit var translationListPresenter: TranslationListPresenter + @Inject + lateinit var audioBarEventRepository: AudioBarEventRepository + @Inject + lateinit var downloadInfoStreams: DownloadInfoStreams + @Inject + lateinit var qariManager: CurrentQariManager + @Inject + lateinit var downloadBridge: DownloadBridge private lateinit var audioStatusRepositoryBridge: AudioStatusRepositoryBridge private lateinit var readingEventPresenterBridge: ReadingEventPresenterBridge @@ -262,8 +293,57 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat // field injection pagerActivityComponent.inject(this) + isFoldableDeviceOpenAndVertical = + savedInstanceState?.getBoolean(LAST_FOLDING_STATE, isFoldableDeviceOpenAndVertical) + ?: isFoldableDeviceOpenAndVertical + + lifecycleScope.launch(scope.coroutineContext) { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + WindowInfoTracker.getOrCreate(this@PagerActivity) + .windowLayoutInfo(this@PagerActivity) + .mapNotNull { it.displayFeatures.filterIsInstance().firstOrNull() } + .collectLatest { foldingFeatures -> + val localState = foldingFeatures.state == FoldingFeature.State.FLAT && + foldingFeatures.orientation == FoldingFeature.Orientation.VERTICAL + if (isFoldableDeviceOpenAndVertical != localState) { + isFoldableDeviceOpenAndVertical = localState + initialize(savedInstanceState) + } + } + } + } + + setContentView(R.layout.quran_page_activity_slider) + initialize(savedInstanceState) + requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean? -> + audioPresenter.onPostNotificationsPermissionResponse( + isGranted!! + ) + } + + // read the list of translations + requestTranslationsList() + + downloadBridge.subscribeToDownloads { + onDownloadSuccess() + } + + bookmarksDao.pageBookmarksWithoutTags().combine(currentPageFlow) { bookmarks, currentPage -> + bookmarks to currentPage + }.onEach { (bookmarks, page) -> + val isBookmarked = if (isDualPages) { + bookmarks.any { it.page == page || it.page == page - 1 } + } else { + bookmarks.any { it.page == page } + } + refreshBookmarksMenu(isBookmarked) + }.launchIn(scope) + } + + private fun initialize(savedInstanceState: Bundle?) { var shouldAdjustPageNumber = false - isDualPages = QuranUtils.isDualPages(this, quranScreenInfo) + isDualPages = QuranUtils.isDualPages(this, quranScreenInfo, isFoldableDeviceOpenAndVertical) isSplitScreen = quranSettings.isQuranSplitWithTranslation audioStatusRepositoryBridge = AudioStatusRepositoryBridge( audioStatusRepository, @@ -323,7 +403,6 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat compositeDisposable = CompositeDisposable() - setContentView(R.layout.quran_page_activity_slider) audioStatusBar = findViewById(R.id.audio_area) audioBarParams = audioStatusBar.layoutParams as MarginLayoutParams @@ -498,31 +577,6 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat { ayah: SuraAyah -> ensurePage(ayah.sura, ayah.ayah) }, { sliderPage: Int -> showSlider(slidingPagerAdapter.getPagePosition(sliderPage)) } )) - - requestPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean? -> - audioPresenter.onPostNotificationsPermissionResponse( - isGranted!! - ) - } - - // read the list of translations - requestTranslationsList() - - downloadBridge.subscribeToDownloads { - onDownloadSuccess() - } - - bookmarksDao.pageBookmarksWithoutTags().combine(currentPageFlow) { bookmarks, currentPage -> - bookmarks to currentPage - }.onEach { (bookmarks, page) -> - val isBookmarked = if (isDualPages) { - bookmarks.any { it.page == page || it.page == page - 1 } - } else { - bookmarks.any { it.page == page } - } - refreshBookmarksMenu(isBookmarked) - }.launchIn(scope) } override fun onRequestPermissionsResult( @@ -551,9 +605,9 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat viewPager.addOnPageChangeListener(pageChangedListener) awaitClose { viewPager.removeOnPageChangeListener(pageChangedListener) } } - .onStart { emit(currentPage) } - .buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) - .shareIn(scope, SharingStarted.Eagerly, 1) + .onStart { emit(currentPage) } + .buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) + .shareIn(scope, SharingStarted.Eagerly, 1) private val statusBarHeight: Int get() { @@ -646,9 +700,17 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat private fun startPosition(ayahSelection: AyahSelection): SuraAyah? { return when (ayahSelection) { - is AyahSelection.Ayah -> { ayahSelection.suraAyah } - is AyahRange -> { ayahSelection.startSuraAyah } - else -> { null } + is AyahSelection.Ayah -> { + ayahSelection.suraAyah + } + + is AyahRange -> { + ayahSelection.startSuraAyah + } + + else -> { + null + } } } @@ -979,6 +1041,7 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat state.putBoolean(LAST_READING_MODE_IS_TRANSLATION, showingTranslation) state.putBoolean(LAST_ACTIONBAR_STATE, isActionBarHidden) state.putBoolean(LAST_WAS_DUAL_PAGES, isDualPages) + state.putBoolean(LAST_FOLDING_STATE, isFoldableDeviceOpenAndVertical) super.onSaveInstanceState(state) } @@ -1839,6 +1902,7 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat private const val LAST_READ_PAGE = "LAST_READ_PAGE" private const val LAST_READING_MODE_IS_TRANSLATION = "LAST_READING_MODE_IS_TRANSLATION" private const val LAST_ACTIONBAR_STATE = "LAST_ACTIONBAR_STATE" + private const val LAST_FOLDING_STATE = "LAST_FOLDING_STATE" const val EXTRA_JUMP_TO_TRANSLATION: String = "jumpToTranslation" const val EXTRA_HIGHLIGHT_SURA: String = "highlightSura" diff --git a/app/src/main/java/com/quran/labs/androidquran/util/QuranUtils.java b/app/src/main/java/com/quran/labs/androidquran/util/QuranUtils.java index 84c8c9fcc4..ca61df59b6 100644 --- a/app/src/main/java/com/quran/labs/androidquran/util/QuranUtils.java +++ b/app/src/main/java/com/quran/labs/androidquran/util/QuranUtils.java @@ -106,12 +106,12 @@ public static String getLocalizedNumber(Context context, int number) { return numberFormat.format(number); } - public static boolean isDualPages(Context context, QuranScreenInfo qsi) { + public static boolean isDualPages(Context context, QuranScreenInfo qsi, boolean isValidFoldableDeviceAndOpen) { if (context != null && qsi != null) { final Resources resources = context.getResources(); - if (qsi.isDualPageMode() && + if ((qsi.isDualPageMode() && resources.getConfiguration().orientation == - Configuration.ORIENTATION_LANDSCAPE) { + Configuration.ORIENTATION_LANDSCAPE) || isValidFoldableDeviceAndOpen) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getBoolean(Constants.PREF_DUAL_PAGE_ENABLED, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0aa3927ca0..5e7629b088 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,7 @@ androidxSwipeRefreshVersion = "1.1.0" androidxPagingVersion = "3.2.1" androidxPagingComposeVersion = "3.2.1" androidxWorkManagerVersion = "2.9.0" +androidxWindowManager = "1.2.0" # firebase firebaseAnalyticsVersion = "21.6.2" @@ -96,7 +97,7 @@ androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version androidx-paging-runtime-ktx = { module = "androidx.paging:paging-runtime-ktx", version.ref = "androidxPagingVersion" } androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidxPagingComposeVersion" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigationVersion" } - +androidx-window = { module = "androidx.window:window", version.ref = "androidxWindowManager" } # compose compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compose-compiler" } compose-foundation = { module = "androidx.compose.foundation:foundation" } From 2f52097f6b620a7f08475995441debe4c5a7048d Mon Sep 17 00:00:00 2001 From: kadirkid Date: Sun, 21 Apr 2024 22:25:11 +0400 Subject: [PATCH 2/2] Revert format --- .../labs/androidquran/ui/PagerActivity.kt | 69 +++++++------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt index df552c2c5f..1ae9b7a90d 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt +++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt @@ -204,52 +204,29 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat private var lastSelectedTranslationAyah: QuranAyahInfo? = null private var lastActivatedLocalTranslations: Array = emptyArray() - @Inject - lateinit var bookmarksDao: BookmarksDao - @Inject - lateinit var recentPagePresenter: RecentPagePresenter - @Inject - lateinit var quranSettings: QuranSettings - @Inject - lateinit var quranScreenInfo: QuranScreenInfo - @Inject - lateinit var arabicDatabaseUtils: ArabicDatabaseUtils - @Inject - lateinit var quranAppUtils: QuranAppUtils - @Inject - lateinit var shareUtil: ShareUtil - @Inject - lateinit var audioUtils: AudioUtils - @Inject - lateinit var quranDisplayData: QuranDisplayData - @Inject - lateinit var quranInfo: QuranInfo - @Inject - lateinit var quranFileUtils: QuranFileUtils - @Inject - lateinit var audioPresenter: AudioPresenter - @Inject - lateinit var quranEventLogger: QuranEventLogger - @Inject - lateinit var audioStatusRepository: AudioStatusRepository - @Inject - lateinit var readingEventPresenter: ReadingEventPresenter - @Inject - lateinit var pageProviderFactoryProvider: PageViewFactoryProvider - @Inject - lateinit var additionalAyahPanels: Set<@JvmSuppressWildcards AyahActionFragmentProvider> - @Inject - lateinit var pagerActivityRecitationPresenter: PagerActivityRecitationPresenter - @Inject - lateinit var translationListPresenter: TranslationListPresenter - @Inject - lateinit var audioBarEventRepository: AudioBarEventRepository - @Inject - lateinit var downloadInfoStreams: DownloadInfoStreams - @Inject - lateinit var qariManager: CurrentQariManager - @Inject - lateinit var downloadBridge: DownloadBridge + @Inject lateinit var bookmarksDao: BookmarksDao + @Inject lateinit var recentPagePresenter: RecentPagePresenter + @Inject lateinit var quranSettings: QuranSettings + @Inject lateinit var quranScreenInfo: QuranScreenInfo + @Inject lateinit var arabicDatabaseUtils: ArabicDatabaseUtils + @Inject lateinit var quranAppUtils: QuranAppUtils + @Inject lateinit var shareUtil: ShareUtil + @Inject lateinit var audioUtils: AudioUtils + @Inject lateinit var quranDisplayData: QuranDisplayData + @Inject lateinit var quranInfo: QuranInfo + @Inject lateinit var quranFileUtils: QuranFileUtils + @Inject lateinit var audioPresenter: AudioPresenter + @Inject lateinit var quranEventLogger: QuranEventLogger + @Inject lateinit var audioStatusRepository: AudioStatusRepository + @Inject lateinit var readingEventPresenter: ReadingEventPresenter + @Inject lateinit var pageProviderFactoryProvider: PageViewFactoryProvider + @Inject lateinit var additionalAyahPanels: Set<@JvmSuppressWildcards AyahActionFragmentProvider> + @Inject lateinit var pagerActivityRecitationPresenter: PagerActivityRecitationPresenter + @Inject lateinit var translationListPresenter: TranslationListPresenter + @Inject lateinit var audioBarEventRepository: AudioBarEventRepository + @Inject lateinit var downloadInfoStreams: DownloadInfoStreams + @Inject lateinit var qariManager: CurrentQariManager + @Inject lateinit var downloadBridge: DownloadBridge private lateinit var audioStatusRepositoryBridge: AudioStatusRepositoryBridge private lateinit var readingEventPresenterBridge: ReadingEventPresenterBridge