Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for dual pages on foldable devices #2670

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no way of injecting this value here so ignoring it for now. @ahmedre Could you elaborate a bit more on the use case here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline, moving forward, will consider moving this check into QuranScreenInfo if possible.

screenInfo.tabletWidthParam
} else {
screenInfo.widthParam
Expand Down
176 changes: 120 additions & 56 deletions app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -196,29 +204,52 @@ class PagerActivity : AppCompatActivity(), AudioBarListener, OnBookmarkTagsUpdat
private var lastSelectedTranslationAyah: QuranAyahInfo? = null
private var lastActivatedLocalTranslations: Array<LocalTranslation> = 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
Expand Down Expand Up @@ -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<FoldingFeature>().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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
}
}
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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" }
Expand Down