Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ import me.xizzhu.android.joshua.core.Constants
import me.xizzhu.android.joshua.core.VerseAnnotation
import me.xizzhu.android.joshua.core.VerseIndex
import me.xizzhu.android.joshua.databinding.ActivityAnnotatedBinding
import me.xizzhu.android.joshua.infra.BaseActivity
import me.xizzhu.android.joshua.infra.onEach
import me.xizzhu.android.joshua.infra.onFailure
import me.xizzhu.android.joshua.infra.onSuccess
import me.xizzhu.android.joshua.infra.*
import me.xizzhu.android.joshua.preview.VersePreviewItem
import me.xizzhu.android.joshua.ui.dialog
import me.xizzhu.android.joshua.ui.fadeIn
Expand All @@ -43,52 +40,54 @@ import javax.inject.Inject

abstract class AnnotatedVersesActivity<V : VerseAnnotation, VM : AnnotatedVersesViewModel<V>>(
@StringRes private val toolbarText: Int
) : BaseActivity<ActivityAnnotatedBinding, VM>(), BookmarkItem.Callback, HighlightItem.Callback, NoteItem.Callback, VersePreviewItem.Callback {
) : BaseActivityV2<ActivityAnnotatedBinding, VM>(), BookmarkItem.Callback, HighlightItem.Callback, NoteItem.Callback, VersePreviewItem.Callback {
@Inject
lateinit var annotatedVersesViewModel: VM

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

observeSettings()
observeSortOrder()
observeAnnotatedVerses()
annotatedVersesViewModel.viewAction().onEach(::onViewAction).launchIn(lifecycleScope)
annotatedVersesViewModel.viewState().onEach(::onViewState).launchIn(lifecycleScope)
initializeToolbar()
}

private fun observeSettings() {
annotatedVersesViewModel.settings().onEach { viewBinding.verseList.setSettings(it) }.launchIn(lifecycleScope)
private fun onViewAction(viewAction: AnnotatedVersesViewModel.ViewAction) = when (viewAction) {
AnnotatedVersesViewModel.ViewAction.OpenReadingScreen -> navigator.navigate(this, Navigator.SCREEN_READING, extrasForOpeningVerse())
AnnotatedVersesViewModel.ViewAction.ShowLoadAnnotatedVersesFailedError -> {
dialog(
false, R.string.dialog_title_error, R.string.dialog_message_failed_to_load_annotated_verses,
{ _, _ -> annotatedVersesViewModel.loadAnnotatedVerses() }, { _, _ -> finish() }
)
}
is AnnotatedVersesViewModel.ViewAction.ShowOpenPreviewFailedError -> {
dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_load_verses, { _, _ -> showPreview(viewAction.verseIndex) })
}
is AnnotatedVersesViewModel.ViewAction.ShowOpenVerseFailedError -> {
dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_select_verse, { _, _ -> openVerse(viewAction.verseToOpen) })
}
is AnnotatedVersesViewModel.ViewAction.ShowPreview -> {
listDialog(viewAction.previewViewData.title, viewAction.previewViewData.settings, viewAction.previewViewData.items, viewAction.previewViewData.currentPosition)
Unit
}
is AnnotatedVersesViewModel.ViewAction.ShowSaveSortOrderFailedError -> {
dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_save_sort_order, { _, _ -> saveSortOrder(viewAction.sortOrderToSave) })
}
}

private fun observeSortOrder() {
annotatedVersesViewModel.sortOrder().onEach { viewBinding.toolbar.setSortOrder(it) }.launchIn(lifecycleScope)
}
private fun onViewState(viewState: AnnotatedVersesViewModel.ViewState) = with(viewBinding) {
viewState.settings?.let { verseList.setSettings(it) }

if (viewState.loading) {
loadingSpinner.fadeIn()
verseList.visibility = View.GONE
} else {
loadingSpinner.visibility = View.GONE
verseList.fadeIn()
}

private fun observeAnnotatedVerses() {
annotatedVersesViewModel.annotatedVerses()
.onEach(
onLoading = {
with(viewBinding) {
loadingSpinner.fadeIn()
verseList.visibility = View.GONE
}
},
onSuccess = {
with(viewBinding) {
verseList.setItems(it.items)
verseList.fadeIn()
loadingSpinner.visibility = View.GONE
}
},
onFailure = {
viewBinding.loadingSpinner.visibility = View.GONE
dialog(
false, R.string.dialog_title_error, R.string.dialog_message_failed_to_load_annotated_verses,
{ _, _ -> annotatedVersesViewModel.loadAnnotatedVerses() }, { _, _ -> finish() }
)
}
)
.launchIn(lifecycleScope)
verseList.setItems(viewState.annotatedVerseItems)
toolbar.setSortOrder(viewState.sortOrder)
}

private fun initializeToolbar() {
Expand All @@ -100,26 +99,18 @@ abstract class AnnotatedVersesActivity<V : VerseAnnotation, VM : AnnotatedVerses

private fun saveSortOrder(@Constants.SortOrder sortOrder: Int) {
annotatedVersesViewModel.saveSortOrder(sortOrder)
.onFailure { dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_save_sort_order, { _, _ -> saveSortOrder(sortOrder) }) }
.launchIn(lifecycleScope)
}

override fun inflateViewBinding(): ActivityAnnotatedBinding = ActivityAnnotatedBinding.inflate(layoutInflater)

override fun viewModel(): VM = annotatedVersesViewModel

override fun openVerse(verseToOpen: VerseIndex) {
annotatedVersesViewModel.saveCurrentVerseIndex(verseToOpen)
.onSuccess { navigator.navigate(this, Navigator.SCREEN_READING, extrasForOpeningVerse()) }
.onFailure { dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_select_verse, { _, _ -> openVerse(verseToOpen) }) }
.launchIn(lifecycleScope)
annotatedVersesViewModel.openVerse(verseToOpen)
}

override fun showPreview(verseIndex: VerseIndex) {
annotatedVersesViewModel.loadVersesForPreview(verseIndex)
.onSuccess { preview -> listDialog(preview.title, preview.settings, preview.items, preview.currentPosition) }
.onFailure { openVerse(verseIndex) } // Very unlikely to fail, so just falls back to open the verse.
.launchIn(lifecycleScope)
annotatedVersesViewModel.showPreview(verseIndex)
}

protected open fun extrasForOpeningVerse(): Bundle? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,13 @@ import android.app.Application
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import me.xizzhu.android.joshua.R
import me.xizzhu.android.joshua.core.BibleReadingManager
import me.xizzhu.android.joshua.core.Constants
import me.xizzhu.android.joshua.core.SettingsManager
import me.xizzhu.android.joshua.core.Verse
import me.xizzhu.android.joshua.core.VerseAnnotation
import me.xizzhu.android.joshua.core.VerseAnnotationManager
import me.xizzhu.android.joshua.core.VerseIndex
import me.xizzhu.android.joshua.infra.BaseViewModel
import me.xizzhu.android.joshua.infra.onFailure
import me.xizzhu.android.joshua.infra.viewData
import me.xizzhu.android.joshua.core.*
import me.xizzhu.android.joshua.infra.BaseViewModelV2
import me.xizzhu.android.joshua.preview.PreviewViewData
import me.xizzhu.android.joshua.preview.loadPreview
import me.xizzhu.android.joshua.preview.loadPreviewV2
import me.xizzhu.android.joshua.preview.toVersePreviewItems
import me.xizzhu.android.joshua.ui.recyclerview.BaseItem
import me.xizzhu.android.joshua.ui.recyclerview.TextItem
Expand All @@ -51,66 +37,110 @@ import me.xizzhu.android.logger.Log
import java.util.Calendar
import kotlin.collections.ArrayList

class AnnotatedVersesViewData(val items: List<BaseItem>)

abstract class AnnotatedVersesViewModel<V : VerseAnnotation>(
private val bibleReadingManager: BibleReadingManager,
private val verseAnnotationManager: VerseAnnotationManager<V>,
@StringRes private val noItemText: Int,
settingsManager: SettingsManager,
application: Application
) : BaseViewModel(settingsManager, application) {
private val annotatedVerses: MutableStateFlow<ViewData<AnnotatedVersesViewData>?> = MutableStateFlow(null)
) : BaseViewModelV2<AnnotatedVersesViewModel.ViewAction, AnnotatedVersesViewModel.ViewState>(
settingsManager = settingsManager,
application = application,
initialViewState = ViewState(
settings = null,
loading = false,
sortOrder = Constants.DEFAULT_SORT_ORDER,
annotatedVerseItems = emptyList(),
),
) {
sealed class ViewAction {
object OpenReadingScreen : ViewAction()
object ShowLoadAnnotatedVersesFailedError : ViewAction()
class ShowOpenPreviewFailedError(val verseIndex: VerseIndex) : ViewAction()
class ShowOpenVerseFailedError(val verseToOpen: VerseIndex) : ViewAction()
class ShowPreview(val previewViewData: PreviewViewData) : ViewAction()
class ShowSaveSortOrderFailedError(@Constants.SortOrder val sortOrderToSave: Int) : ViewAction()
}

data class ViewState(
val settings: Settings?,
val loading: Boolean,
@Constants.SortOrder val sortOrder: Int,
val annotatedVerseItems: List<BaseItem>,
)

init {
settings().onEach { settings -> emitViewState { it.copy(settings = settings) } }.launchIn(viewModelScope)

combine(
bibleReadingManager.currentTranslation().filter { it.isNotEmpty() },
verseAnnotationManager.sortOrder()
) { currentTranslation, sortOrder ->
try {
annotatedVerses.value = ViewData.Loading()
annotatedVerses.value = ViewData.Success(loadAnnotatedVerses(currentTranslation, sortOrder))
emitViewState { currentViewState ->
currentViewState.copy(loading = true, sortOrder = sortOrder, annotatedVerseItems = emptyList())
}

val items = loadAnnotatedVerses(currentTranslation, sortOrder)
emitViewState { currentViewState ->
currentViewState.copy(loading = false, annotatedVerseItems = items)
}
} catch (e: Exception) {
Log.e(tag, "Error occurred while loading annotated verses", e)
annotatedVerses.value = ViewData.Failure(e)
emitViewAction(ViewAction.ShowLoadAnnotatedVersesFailedError)
emitViewState { currentViewState ->
currentViewState.copy(loading = false, annotatedVerseItems = emptyList())
}
}
loadAnnotatedVerses(currentTranslation, sortOrder)
}.launchIn(viewModelScope)
}

fun sortOrder(): Flow<Int> = verseAnnotationManager.sortOrder()

fun saveSortOrder(@Constants.SortOrder sortOrder: Int): Flow<ViewData<Unit>> = viewData {
verseAnnotationManager.saveSortOrder(sortOrder)
}.onFailure { Log.e(tag, "Failed to save sort order", it) }

fun annotatedVerses(): Flow<ViewData<AnnotatedVersesViewData>> = annotatedVerses.filterNotNull()
fun saveSortOrder(@Constants.SortOrder sortOrder: Int) {
viewModelScope.launch {
try {
verseAnnotationManager.saveSortOrder(sortOrder)
emitViewState { currentViewState ->
currentViewState.copy(sortOrder = sortOrder)
}
} catch (e: Exception) {
Log.e(tag, "Failed to save sort order", e)
emitViewAction(ViewAction.ShowSaveSortOrderFailedError(sortOrder))
}
}
}

fun loadAnnotatedVerses() {
viewModelScope.launch {
try {
annotatedVerses.value = ViewData.Loading()
annotatedVerses.value = ViewData.Success(loadAnnotatedVerses(
emitViewState { currentViewState ->
currentViewState.copy(loading = true, annotatedVerseItems = emptyList())
}

val items = loadAnnotatedVerses(
currentTranslation = bibleReadingManager.currentTranslation().firstNotEmpty(),
sortOrder = verseAnnotationManager.sortOrder().first()
))
)
emitViewState { currentViewState ->
currentViewState.copy(loading = false, annotatedVerseItems = items)
}
} catch (e: Exception) {
Log.e(tag, "Error occurred while loading annotated verses", e)
annotatedVerses.value = ViewData.Failure(e)
emitViewAction(ViewAction.ShowLoadAnnotatedVersesFailedError)
emitViewState { currentViewState ->
currentViewState.copy(loading = false, annotatedVerseItems = emptyList())
}
}
}
}

private suspend fun loadAnnotatedVerses(currentTranslation: String, @Constants.SortOrder sortOrder: Int): AnnotatedVersesViewData {
private suspend fun loadAnnotatedVerses(currentTranslation: String, @Constants.SortOrder sortOrder: Int): List<BaseItem> {
val annotations = verseAnnotationManager.read(sortOrder)
val verses = bibleReadingManager.readVerses(currentTranslation, annotations.map { it.verseIndex })
return AnnotatedVersesViewData(
items = buildAnnotatedVersesItems(
sortOrder = sortOrder,
verses = annotations.mapNotNull { annotation -> verses[annotation.verseIndex]?.let { Pair(annotation, it) } },
bookNames = bibleReadingManager.readBookNames(currentTranslation),
bookShortNames = bibleReadingManager.readBookShortNames(currentTranslation)
)
return buildAnnotatedVersesItems(
sortOrder = sortOrder,
verses = annotations.mapNotNull { annotation -> verses[annotation.verseIndex]?.let { Pair(annotation, it) } },
bookNames = bibleReadingManager.readBookNames(currentTranslation),
bookShortNames = bibleReadingManager.readBookShortNames(currentTranslation)
)
}

Expand Down Expand Up @@ -192,11 +222,28 @@ abstract class AnnotatedVersesViewModel<V : VerseAnnotation>(
return items
}

fun saveCurrentVerseIndex(verseToOpen: VerseIndex): Flow<ViewData<Unit>> = viewData {
bibleReadingManager.saveCurrentVerseIndex(verseToOpen)
}.onFailure { Log.e(tag, "Failed to save current verse", it) }
fun openVerse(verseToOpen: VerseIndex) {
viewModelScope.launch {
try {
bibleReadingManager.saveCurrentVerseIndex(verseToOpen)
emitViewAction(ViewAction.OpenReadingScreen)
} catch (e: Exception) {
Log.e(tag, "Failed to save current verse", e)
emitViewAction(ViewAction.ShowOpenVerseFailedError(verseToOpen))
}
}
}

fun loadVersesForPreview(verseIndex: VerseIndex): Flow<ViewData<PreviewViewData>> =
loadPreview(bibleReadingManager, settingsManager, verseIndex, ::toVersePreviewItems)
.onFailure { Log.e(tag, "Failed to load verses for preview", it) }
fun showPreview(verseIndex: VerseIndex) {
viewModelScope.launch {
try {
emitViewAction(ViewAction.ShowPreview(
previewViewData = loadPreviewV2(bibleReadingManager, settingsManager, verseIndex, ::toVersePreviewItems)
))
} catch (e: Exception) {
Log.e(tag, "Failed to load verses for preview", e)
emitViewAction(ViewAction.ShowOpenPreviewFailedError(verseIndex))
}
}
}
}
Loading