From e19daf6ea9529e91a5f5cf86678a8c6146d3e13a Mon Sep 17 00:00:00 2001 From: aabdurrahman <49524354+aabdurrahman@users.noreply.github.com> Date: Thu, 3 Mar 2022 10:15:24 -0500 Subject: [PATCH] Add recitation module --- app/build.gradle | 1 + app/pluggable.gradle | 1 + .../events/RecitationEventPresenter.kt | 52 +++++++++++++++++++ .../RecitationPlaybackEventPresenter.kt | 33 ++++++++++++ .../recitation/events/RecitationSelection.kt | 44 ++++++++++++++++ .../quran/recitation/presenter/Presenter.kt | 6 +++ .../RecitationHighlightsPresenter.kt | 26 ++++++++++ .../presenter/RecitationPlaybackPresenter.kt | 17 ++++++ .../presenter/RecitationPopupPresenter.kt | 20 +++++++ .../presenter/RecitationPresenter.kt | 19 +++++++ .../presenter/RecitationSettings.kt | 8 +++ feature/recitation/build.gradle | 28 ++++++++++ .../recitation/src/main/AndroidManifest.xml | 1 + .../RecitationHighlightsPresenterImpl.kt | 17 ++++++ .../RecitationPlaybackPresenterImpl.kt | 24 +++++++++ .../presenter/RecitationPopupPresenterImpl.kt | 15 ++++++ .../presenter/RecitationPresenterImpl.kt | 25 +++++++++ .../presenter/RecitationSettingsImpl.kt | 14 +++++ settings.gradle | 1 + 19 files changed, 352 insertions(+) create mode 100644 common/recitation/src/main/java/com/quran/recitation/events/RecitationEventPresenter.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/events/RecitationPlaybackEventPresenter.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/events/RecitationSelection.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/presenter/Presenter.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/presenter/RecitationHighlightsPresenter.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPlaybackPresenter.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPopupPresenter.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPresenter.kt create mode 100644 common/recitation/src/main/java/com/quran/recitation/presenter/RecitationSettings.kt create mode 100644 feature/recitation/build.gradle create mode 100644 feature/recitation/src/main/AndroidManifest.xml create mode 100644 feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationHighlightsPresenterImpl.kt create mode 100644 feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPlaybackPresenterImpl.kt create mode 100644 feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPopupPresenterImpl.kt create mode 100644 feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPresenterImpl.kt create mode 100644 feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationSettingsImpl.kt diff --git a/app/build.gradle b/app/build.gradle index 1d49f5660c..57126c93a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -120,6 +120,7 @@ dependencies { implementation project(path: ':common:networking') implementation project(path: ':common:pages') implementation project(path: ':common:reading') + implementation project(path: ':common:recitation') implementation project(path: ':common:search') implementation project(path: ':common:toolbar') implementation project(path: ':common:upgrade') diff --git a/app/pluggable.gradle b/app/pluggable.gradle index 10b55e57d9..0d6676a1d8 100644 --- a/app/pluggable.gradle +++ b/app/pluggable.gradle @@ -1,4 +1,5 @@ dependencies { implementation project(path: ':pages:madani') implementation project(path: ':feature:analytics-noop') + implementation project(path: ':feature:recitation') } diff --git a/common/recitation/src/main/java/com/quran/recitation/events/RecitationEventPresenter.kt b/common/recitation/src/main/java/com/quran/recitation/events/RecitationEventPresenter.kt new file mode 100644 index 0000000000..553a1b156f --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/events/RecitationEventPresenter.kt @@ -0,0 +1,52 @@ +package com.quran.recitation.events + +import com.quran.data.model.SuraAyah +import com.quran.recitation.common.RecitationSession +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RecitationEventPresenter @Inject constructor() { + private val _recitationChangeFlow = MutableSharedFlow( + replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + private val _listeningStateFlow = MutableStateFlow(false) + private val _recitationSessionFlow = MutableStateFlow(null) + private val _recitationSelectionFlow = MutableStateFlow(RecitationSelection.None) + + val recitationChangeFlow: Flow = _recitationChangeFlow.asSharedFlow() + val listeningStateFlow: StateFlow = _listeningStateFlow.asStateFlow() + val recitationSessionFlow: StateFlow = _recitationSessionFlow.asStateFlow() + val recitationSelectionFlow: StateFlow = _recitationSelectionFlow.asStateFlow() + + fun onRecitationChange(ayah: SuraAyah) { + _recitationChangeFlow.tryEmit(ayah) + } + + fun isListening(): Boolean = listeningStateFlow.value + fun recitationSession(): RecitationSession? = recitationSessionFlow.value + fun hasRecitationSession(): Boolean = recitationSession() != null + + fun onListeningStateChange(isListening: Boolean) { + _listeningStateFlow.value = isListening + } + + fun onRecitationSessionChange(session: RecitationSession?) { + // Whenever the session changes, also clear any selections + if (session != _recitationSessionFlow.value) { + _recitationSelectionFlow.value = RecitationSelection.None + _recitationSessionFlow.value = session + } + } + + fun onRecitationSelection(selection: RecitationSelection) { + _recitationSelectionFlow.tryEmit(selection) + } + +} diff --git a/common/recitation/src/main/java/com/quran/recitation/events/RecitationPlaybackEventPresenter.kt b/common/recitation/src/main/java/com/quran/recitation/events/RecitationPlaybackEventPresenter.kt new file mode 100644 index 0000000000..e5157d2933 --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/events/RecitationPlaybackEventPresenter.kt @@ -0,0 +1,33 @@ +package com.quran.recitation.events + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RecitationPlaybackEventPresenter @Inject constructor() { + + private val _loadedRecitationFlow = MutableStateFlow(null) + private val _playingStateFlow = MutableStateFlow(false) + private val _playbackPositionFlow = MutableStateFlow(0) + val loadedRecitationFlow: StateFlow = _loadedRecitationFlow.asStateFlow() + val playingStateFlow: StateFlow = _playingStateFlow.asStateFlow() + val playbackPositionFlow: StateFlow = _playbackPositionFlow.asStateFlow() + + fun isPlaying(): Boolean = playingStateFlow.value + + fun onLoadedRecitationChange(loadedRecitation: String?) { + _loadedRecitationFlow.value = loadedRecitation + } + + fun onPlayingStateChange(playingState: Boolean) { + _playingStateFlow.value = playingState + } + + fun onPlaybackPositionChange(playbackPosition: Int) { + _playbackPositionFlow.value = playbackPosition + } + +} diff --git a/common/recitation/src/main/java/com/quran/recitation/events/RecitationSelection.kt b/common/recitation/src/main/java/com/quran/recitation/events/RecitationSelection.kt new file mode 100644 index 0000000000..bab05a10dd --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/events/RecitationSelection.kt @@ -0,0 +1,44 @@ +package com.quran.recitation.events + +import com.quran.data.model.SuraAyah +import com.quran.recitation.common.RecitedAyah +import com.quran.recitation.common.RecitedSection +import com.quran.recitation.common.RecitedWord + +sealed class RecitationSelection { + object None : RecitationSelection() + + data class Ayah( + val ayah: RecitedAyah, + ): RecitationSelection() + + data class Section( + val section: RecitedSection, + ): RecitationSelection() + + data class Word( + val section: RecitedSection, + val word: RecitedWord, + ): RecitationSelection() + + fun ayah(): SuraAyah? = when (this) { + is Ayah -> ayah.ayah() + is Section -> section.ayah() + is Word -> section.ayah() + None -> null + } + + fun startTime(): Double? = when (this) { + is Ayah -> ayah.startTime() + is Section -> section.startTime() + is Word -> word.startTime() + None -> null + } + + fun endTime(): Double? = when (this) { + is Ayah -> ayah.endTime() + is Section -> section.endTime() + is Word -> word.endTime() + None -> null + } +} diff --git a/common/recitation/src/main/java/com/quran/recitation/presenter/Presenter.kt b/common/recitation/src/main/java/com/quran/recitation/presenter/Presenter.kt new file mode 100644 index 0000000000..501c4c83ce --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/presenter/Presenter.kt @@ -0,0 +1,6 @@ +package com.quran.recitation.presenter + +interface Presenter { + fun bind(what: T) + fun unbind(what: T) +} diff --git a/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationHighlightsPresenter.kt b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationHighlightsPresenter.kt new file mode 100644 index 0000000000..7b248ca965 --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationHighlightsPresenter.kt @@ -0,0 +1,26 @@ +package com.quran.recitation.presenter + +import com.quran.data.model.highlight.HighlightInfo +import com.quran.data.model.highlight.HighlightType +import com.quran.recitation.presenter.RecitationHighlightsPresenter.RecitationPage + +interface RecitationHighlightsPresenter : Presenter { + + override fun bind(what: RecitationPage) + override fun unbind(what: RecitationPage) + + /** Force a refresh of highlights */ + fun refresh() + + interface RecitationPage { + val pageNumbers: Set + fun applyHighlights(highlights: List) + } + + sealed class HighlightAction { + data class Highlight(val highlightInfo: HighlightInfo): HighlightAction() + data class Unhighlight(val highlightInfo: HighlightInfo): HighlightAction() + data class UnhighlightAll(val highlightType: HighlightType, val page: Int? = null): HighlightAction() + } + +} diff --git a/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPlaybackPresenter.kt b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPlaybackPresenter.kt new file mode 100644 index 0000000000..95aa507d52 --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPlaybackPresenter.kt @@ -0,0 +1,17 @@ +package com.quran.recitation.presenter + +import android.app.Activity +import com.quran.recitation.events.RecitationSelection +import kotlinx.coroutines.flow.StateFlow + +interface RecitationPlaybackPresenter : Presenter { + + val recitationPlaybackFlow: StateFlow + + override fun bind(what: Activity) + override fun unbind(what: Activity) + + fun play() + fun pauseIfPlaying() + +} diff --git a/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPopupPresenter.kt b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPopupPresenter.kt new file mode 100644 index 0000000000..415076c944 --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPopupPresenter.kt @@ -0,0 +1,20 @@ +package com.quran.recitation.presenter + +import android.graphics.RectF +import android.widget.ImageView +import com.quran.data.model.AyahWord +import com.quran.data.model.selection.SelectionIndicator +import com.quran.recitation.presenter.RecitationPopupPresenter.PopupContainer + +interface RecitationPopupPresenter : Presenter { + + override fun bind(what: PopupContainer) + override fun unbind(what: PopupContainer) + + interface PopupContainer { + fun getQuranPageImageView(page: Int): ImageView? + fun getSelectionBoundsForWord(page: Int, word: AyahWord): SelectionIndicator.SelectedItemPosition? + fun getBoundsForWord(word: AyahWord): List? + } + +} diff --git a/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPresenter.kt b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPresenter.kt new file mode 100644 index 0000000000..3bea185b72 --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationPresenter.kt @@ -0,0 +1,19 @@ +package com.quran.recitation.presenter + +import android.app.Activity +import com.quran.data.model.SuraAyah +import kotlinx.coroutines.flow.StateFlow + +interface RecitationPresenter : Presenter { + + override fun bind(what: Activity) + override fun unbind(what: Activity) + + fun isRecitationEnabled(): Boolean + fun isRecitationEnabledFlow(): StateFlow + fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) + + fun startOrStopRecitation(startAyah: () -> SuraAyah, showModeSelection: Boolean = false) + fun endSession() + +} diff --git a/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationSettings.kt b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationSettings.kt new file mode 100644 index 0000000000..18bfa199e9 --- /dev/null +++ b/common/recitation/src/main/java/com/quran/recitation/presenter/RecitationSettings.kt @@ -0,0 +1,8 @@ +package com.quran.recitation.presenter + +interface RecitationSettings { + + fun isRecitationEnabled(): Boolean + fun toggleAyahVisibility() + +} diff --git a/feature/recitation/build.gradle b/feature/recitation/build.gradle new file mode 100644 index 0000000000..2c0270a0da --- /dev/null +++ b/feature/recitation/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'com.squareup.anvil' +} + +android { + compileSdkVersion deps.android.build.compileSdkVersion + defaultConfig { + minSdkVersion deps.android.build.minSdkVersion + targetSdkVersion deps.android.build.targetSdkVersion + } +} + +anvil { generateDaggerFactories = true } + +dependencies { + implementation project(path: ':common:data') + implementation project(path: ':common:recitation') + implementation "androidx.annotation:annotation:${androidxAnnotationVersion}" + + // dagger + implementation deps.dagger.runtime + + // coroutines + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${coroutinesVersion}" +} diff --git a/feature/recitation/src/main/AndroidManifest.xml b/feature/recitation/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..522afcae1f --- /dev/null +++ b/feature/recitation/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationHighlightsPresenterImpl.kt b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationHighlightsPresenterImpl.kt new file mode 100644 index 0000000000..a8df0045c8 --- /dev/null +++ b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationHighlightsPresenterImpl.kt @@ -0,0 +1,17 @@ +package com.quran.mobile.recitation.presenter + +import com.quran.data.di.QuranPageScope +import com.quran.data.di.QuranReadingPageScope +import com.quran.recitation.presenter.RecitationHighlightsPresenter +import com.quran.recitation.presenter.RecitationHighlightsPresenter.RecitationPage +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +@QuranPageScope +@ContributesBinding(scope = QuranReadingPageScope::class, boundType = RecitationHighlightsPresenter::class) +class RecitationHighlightsPresenterImpl @Inject constructor(): RecitationHighlightsPresenter { + override fun bind(what: RecitationPage) {} + override fun unbind(what: RecitationPage) {} + + override fun refresh() {} +} diff --git a/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPlaybackPresenterImpl.kt b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPlaybackPresenterImpl.kt new file mode 100644 index 0000000000..6b8269a6fc --- /dev/null +++ b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPlaybackPresenterImpl.kt @@ -0,0 +1,24 @@ +package com.quran.mobile.recitation.presenter + +import android.app.Activity +import com.quran.data.di.ActivityScope +import com.quran.data.di.QuranReadingScope +import com.quran.recitation.presenter.RecitationPlaybackPresenter +import com.quran.recitation.events.RecitationSelection +import com.squareup.anvil.annotations.ContributesBinding +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ActivityScope +@ContributesBinding(scope = QuranReadingScope::class, boundType = RecitationPlaybackPresenter::class) +class RecitationPlaybackPresenterImpl @Inject constructor(): RecitationPlaybackPresenter { + override val recitationPlaybackFlow: StateFlow = + MutableStateFlow(RecitationSelection.None) + + override fun bind(what: Activity) {} + override fun unbind(what: Activity) {} + + override fun play() {} + override fun pauseIfPlaying() {} +} diff --git a/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPopupPresenterImpl.kt b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPopupPresenterImpl.kt new file mode 100644 index 0000000000..a3112ae0df --- /dev/null +++ b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPopupPresenterImpl.kt @@ -0,0 +1,15 @@ +package com.quran.mobile.recitation.presenter + +import com.quran.data.di.QuranPageScope +import com.quran.data.di.QuranReadingPageScope +import com.quran.recitation.presenter.RecitationPopupPresenter +import com.quran.recitation.presenter.RecitationPopupPresenter.PopupContainer +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +@QuranPageScope +@ContributesBinding(scope = QuranReadingPageScope::class, boundType = RecitationPopupPresenter::class) +class RecitationPopupPresenterImpl @Inject constructor(): RecitationPopupPresenter { + override fun bind(what: PopupContainer) {} + override fun unbind(what: PopupContainer) {} +} diff --git a/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPresenterImpl.kt b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPresenterImpl.kt new file mode 100644 index 0000000000..33bcb97d63 --- /dev/null +++ b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationPresenterImpl.kt @@ -0,0 +1,25 @@ +package com.quran.mobile.recitation.presenter + +import android.app.Activity +import com.quran.data.di.ActivityScope +import com.quran.data.di.QuranReadingScope +import com.quran.data.model.SuraAyah +import com.quran.recitation.presenter.RecitationPresenter +import com.squareup.anvil.annotations.ContributesBinding +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ActivityScope +@ContributesBinding(scope = QuranReadingScope::class, boundType = RecitationPresenter::class) +class RecitationPresenterImpl @Inject constructor(): RecitationPresenter { + override fun bind(what: Activity) {} + override fun unbind(what: Activity) {} + + override fun isRecitationEnabled(): Boolean = false + override fun isRecitationEnabledFlow(): StateFlow = MutableStateFlow(false) + override fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) {} + + override fun startOrStopRecitation(startAyah: () -> SuraAyah, showModeSelection: Boolean) {} + override fun endSession() {} +} diff --git a/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationSettingsImpl.kt b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationSettingsImpl.kt new file mode 100644 index 0000000000..7c12f5a725 --- /dev/null +++ b/feature/recitation/src/main/java/com/quran/mobile/recitation/presenter/RecitationSettingsImpl.kt @@ -0,0 +1,14 @@ +package com.quran.mobile.recitation.presenter + +import com.quran.data.di.AppScope +import com.quran.recitation.presenter.RecitationSettings +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +@ContributesBinding(scope = AppScope::class, boundType = RecitationSettings::class) +class RecitationSettingsImpl @Inject constructor() : RecitationSettings { + override fun isRecitationEnabled() = false + override fun toggleAyahVisibility() {} +} diff --git a/settings.gradle b/settings.gradle index 7b045bb031..8c3ea9c717 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ include ':common:toolbar' include ':common:upgrade' include ':feature:analytics-noop' include ':feature:audio' +include ':feature:recitation' include ':pages:madani' if (new File(rootDir, 'extras/settings-extra.gradle').exists()) {