diff --git a/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/DownloadableLanguagesLayout.kt b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/DownloadableLanguagesLayout.kt index d2a1be5f91..e4ef05b33c 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/DownloadableLanguagesLayout.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/DownloadableLanguagesLayout.kt @@ -2,6 +2,7 @@ package org.cru.godtools.ui.languages.downloadable import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth @@ -18,7 +19,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBar -import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -129,11 +129,18 @@ private fun LanguageListItem(viewModel: LanguageViewModels.LanguageViewModel, mo Text(pluralStringResource(R.plurals.language_settings_downloadable_languages_available_tools, tools, tools)) }, trailingContent = { - Switch(language.isAdded, onCheckedChange = { - scope.launch(NonCancellable) { - if (it) viewModel.pin() else viewModel.unpin() + val toolsDownloaded by viewModel.toolsDownloaded.collectAsState() + + LanguageDownloadProgressIndicator( + language.isAdded, + downloaded = toolsDownloaded, + total = toolsAvailable, + modifier = Modifier.clickable { + scope.launch(NonCancellable) { + if (language.isAdded) viewModel.unpin() else viewModel.pin() + } } - }) + ) }, modifier = modifier ) diff --git a/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageDownloadProgressIndicator+Preview.kt b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageDownloadProgressIndicator+Preview.kt new file mode 100644 index 0000000000..77cc331859 --- /dev/null +++ b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageDownloadProgressIndicator+Preview.kt @@ -0,0 +1,23 @@ +package org.cru.godtools.ui.languages.downloadable + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import org.cru.godtools.base.ui.theme.GodToolsTheme + +@Preview +@Composable +private fun LanguageDownloadProgressIndicatorNotPinned() { + GodToolsTheme { LanguageDownloadProgressIndicator(isPinned = false, downloaded = 0, total = 5) } +} + +@Preview +@Composable +private fun LanguageDownloadProgressIndicatorInProgress() { + GodToolsTheme { LanguageDownloadProgressIndicator(isPinned = true, downloaded = 2, total = 5) } +} + +@Preview +@Composable +private fun LanguageDownloadProgressIndicatorDownloaded() { + GodToolsTheme { LanguageDownloadProgressIndicator(isPinned = true, downloaded = 5, total = 5) } +} diff --git a/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageDownloadProgressIndicator.kt b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageDownloadProgressIndicator.kt new file mode 100644 index 0000000000..c155dca746 --- /dev/null +++ b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageDownloadProgressIndicator.kt @@ -0,0 +1,65 @@ +package org.cru.godtools.ui.languages.downloadable + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CheckCircle +import androidx.compose.material.icons.outlined.DownloadForOffline +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +internal fun LanguageDownloadProgressIndicator( + isPinned: Boolean, + downloaded: Int, + total: Int, + modifier: Modifier = Modifier, + iconSize: Dp = 24.dp, +) { + val total = total.coerceAtLeast(0) + val downloaded = downloaded.coerceIn(0, total) + val contentModifier = modifier.size(iconSize) + + when { + !isPinned -> Icon( + Icons.Outlined.DownloadForOffline, + null, + modifier = contentModifier, + tint = MaterialTheme.colorScheme.outline, + ) + downloaded == total -> Icon( + Icons.Outlined.CheckCircle, + null, + modifier = contentModifier, + tint = MaterialTheme.colorScheme.primary, + ) + else -> { + val progress by animateFloatAsState( + label = "Download Progress", + targetValue = when (total) { + 0 -> 1f + else -> downloaded.toFloat() / total + }, + ) + + val iconPadding = iconSize / 12 + CircularProgressIndicator( + progress = progress, + color = MaterialTheme.colorScheme.primary, + strokeWidth = (iconSize / 2) - iconPadding, + modifier = contentModifier + .padding(iconPadding) + .border(iconSize / 12, MaterialTheme.colorScheme.primary, CircleShape) + ) + } + } +} diff --git a/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageViewModels.kt b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageViewModels.kt index e21ceb29ac..ccece87f4c 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageViewModels.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/languages/downloadable/LanguageViewModels.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.stateIn import org.cru.godtools.db.repository.LanguagesRepository import org.cru.godtools.db.repository.ToolsRepository import org.cru.godtools.model.Language +import org.cru.godtools.model.Tool @HiltViewModel class LanguageViewModels @Inject constructor( @@ -32,6 +33,10 @@ class LanguageViewModels @Inject constructor( .map { it.size } .flowOn(Dispatchers.Default) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), 0) + val toolsDownloaded = toolsRepository.getDownloadedToolsFlowByTypesAndLanguage(Tool.Type.NORMAL_TYPES, code) + .map { it.size } + .flowOn(Dispatchers.Default) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), 0) suspend fun pin() = languagesRepository.pinLanguage(code) suspend fun unpin() = languagesRepository.unpinLanguage(code) diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/repository/ToolsRepository.kt b/library/db/src/main/kotlin/org/cru/godtools/db/repository/ToolsRepository.kt index 05a0ff7cea..340105c301 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/repository/ToolsRepository.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/repository/ToolsRepository.kt @@ -17,6 +17,7 @@ interface ToolsRepository { fun getToolsFlowByType(vararg types: Tool.Type) = getToolsFlowByType(types.toSet()) fun getToolsFlowByType(types: Collection): Flow> fun getToolsFlowForLanguage(locale: Locale): Flow> + fun getDownloadedToolsFlowByTypesAndLanguage(types: Collection, locale: Locale): Flow> fun getMetaToolsFlow() = getToolsFlowByType(Tool.Type.META) fun getLessonsFlow() = getToolsFlowByType(Tool.Type.LESSON) fun getNormalToolsFlow() = getToolsFlowByType(Tool.Type.NORMAL_TYPES) diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt index da23385415..6c153e9d19 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt @@ -37,6 +37,15 @@ internal interface ToolsDao { "SELECT * FROM tools WHERE type in (:types) AND code IN (SELECT tool FROM translations WHERE locale = :locale)" ) fun getToolsFlowByTypeAndLanguage(types: Collection, locale: Locale): Flow> + @Query( + """ + SELECT * FROM tools + WHERE + type in (:types) AND + code IN (SELECT tool FROM translations WHERE locale = :locale AND isDownloaded = 1) + """ + ) + fun getDownloadedToolsFlowByTypeAndLanguage(types: Collection, locale: Locale): Flow> @Query("SELECT * FROM tools") suspend fun getToolFavorites(): List diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt index ba19e0625b..39a4fc0d59 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt @@ -27,6 +27,8 @@ internal abstract class ToolsRoomRepository(private val db: GodToolsRoomDatabase dao.getToolsByTypeFlow(types).map { it.map { it.toModel() } } override fun getToolsFlowForLanguage(locale: Locale) = dao.getToolsFlowByTypeAndLanguage(Tool.Type.NORMAL_TYPES, locale).map { it.map { it.toModel() } } + override fun getDownloadedToolsFlowByTypesAndLanguage(types: Collection, locale: Locale) = + dao.getDownloadedToolsFlowByTypeAndLanguage(types, locale).map { it.map { it.toModel() } } override fun toolsChangeFlow(): Flow = db.changeFlow("tools") diff --git a/library/db/src/test/kotlin/org/cru/godtools/db/repository/ToolsRepositoryIT.kt b/library/db/src/test/kotlin/org/cru/godtools/db/repository/ToolsRepositoryIT.kt index c479399719..e4d5afb686 100644 --- a/library/db/src/test/kotlin/org/cru/godtools/db/repository/ToolsRepositoryIT.kt +++ b/library/db/src/test/kotlin/org/cru/godtools/db/repository/ToolsRepositoryIT.kt @@ -180,6 +180,29 @@ abstract class ToolsRepositoryIT { } // endregion getToolsFlowForLanguage() + // region getDownloadedToolsFlowByTypesAndLanguage() + @Test + fun `getDownloadedToolsFlowByTypesAndLanguage()`() = testScope.runTest { + val tool1 = Tool("tool1") + val tool2 = Tool("tool2") + repository.storeInitialTools(listOf(tool1, tool2)) + languagesRepository.storeInitialLanguages(listOf(Language(Locale.ENGLISH), Language(Locale.FRENCH))) + + repository.getDownloadedToolsFlowByTypesAndLanguage(Tool.Type.NORMAL_TYPES, Locale.ENGLISH).test { + assertTrue(awaitItem().isEmpty()) + + translationsRepository.storeInitialTranslations( + listOf( + Translation("tool1", Locale.ENGLISH, isDownloaded = true), + Translation("tool2", Locale.ENGLISH, isDownloaded = false), + Translation("tool2", Locale.FRENCH, isDownloaded = true), + ) + ) + assertThat(awaitItem(), contains(tool(tool1))) + } + } + // endregion getDownloadedToolsFlowByTypesAndLanguage() + // region getFavoriteToolsFlow() @Test fun `getFavoriteToolsFlow()`() = testScope.runTest {