Skip to content

Commit

Permalink
Add recitation module
Browse files Browse the repository at this point in the history
  • Loading branch information
aabdurrahman committed Mar 4, 2022
1 parent 6b8cac3 commit e19daf6
Show file tree
Hide file tree
Showing 19 changed files with 352 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions app/pluggable.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dependencies {
implementation project(path: ':pages:madani')
implementation project(path: ':feature:analytics-noop')
implementation project(path: ':feature:recitation')
}
Original file line number Diff line number Diff line change
@@ -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<SuraAyah>(
replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val _listeningStateFlow = MutableStateFlow<Boolean>(false)
private val _recitationSessionFlow = MutableStateFlow<RecitationSession?>(null)
private val _recitationSelectionFlow = MutableStateFlow<RecitationSelection>(RecitationSelection.None)

val recitationChangeFlow: Flow<SuraAyah> = _recitationChangeFlow.asSharedFlow()
val listeningStateFlow: StateFlow<Boolean> = _listeningStateFlow.asStateFlow()
val recitationSessionFlow: StateFlow<RecitationSession?> = _recitationSessionFlow.asStateFlow()
val recitationSelectionFlow: StateFlow<RecitationSelection> = _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)
}

}
Original file line number Diff line number Diff line change
@@ -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<String?>(null)
private val _playingStateFlow = MutableStateFlow<Boolean>(false)
private val _playbackPositionFlow = MutableStateFlow<Int>(0)
val loadedRecitationFlow: StateFlow<String?> = _loadedRecitationFlow.asStateFlow()
val playingStateFlow: StateFlow<Boolean> = _playingStateFlow.asStateFlow()
val playbackPositionFlow: StateFlow<Int> = _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
}

}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.quran.recitation.presenter

interface Presenter<T : Any> {
fun bind(what: T)
fun unbind(what: T)
}
Original file line number Diff line number Diff line change
@@ -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<RecitationPage> {

override fun bind(what: RecitationPage)
override fun unbind(what: RecitationPage)

/** Force a refresh of highlights */
fun refresh()

interface RecitationPage {
val pageNumbers: Set<Int>
fun applyHighlights(highlights: List<HighlightAction>)
}

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()
}

}
Original file line number Diff line number Diff line change
@@ -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<Activity> {

val recitationPlaybackFlow: StateFlow<RecitationSelection>

override fun bind(what: Activity)
override fun unbind(what: Activity)

fun play()
fun pauseIfPlaying()

}
Original file line number Diff line number Diff line change
@@ -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<PopupContainer> {

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<RectF>?
}

}
Original file line number Diff line number Diff line change
@@ -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<Activity> {

override fun bind(what: Activity)
override fun unbind(what: Activity)

fun isRecitationEnabled(): Boolean
fun isRecitationEnabledFlow(): StateFlow<Boolean>
fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray)

fun startOrStopRecitation(startAyah: () -> SuraAyah, showModeSelection: Boolean = false)
fun endSession()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.quran.recitation.presenter

interface RecitationSettings {

fun isRecitationEnabled(): Boolean
fun toggleAyahVisibility()

}
28 changes: 28 additions & 0 deletions feature/recitation/build.gradle
Original file line number Diff line number Diff line change
@@ -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}"
}
1 change: 1 addition & 0 deletions feature/recitation/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.quran.mobile.recitation"/>
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
@@ -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<RecitationSelection> =
MutableStateFlow(RecitationSelection.None)

override fun bind(what: Activity) {}
override fun unbind(what: Activity) {}

override fun play() {}
override fun pauseIfPlaying() {}
}
Original file line number Diff line number Diff line change
@@ -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) {}
}
Original file line number Diff line number Diff line change
@@ -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<Boolean> = MutableStateFlow(false)
override fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) {}

override fun startOrStopRecitation(startAyah: () -> SuraAyah, showModeSelection: Boolean) {}
override fun endSession() {}
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down

0 comments on commit e19daf6

Please sign in to comment.