Skip to content

Commit

Permalink
Merge pull request #3229 from CruGlobal/downloadLanguageIndicator
Browse files Browse the repository at this point in the history
GT-2198 Add a download progress indicator for the downloadable languages UI
  • Loading branch information
frett authored Nov 17, 2023
2 parents dbd0b75 + 879f2bb commit 99bd468
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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) }
}
Original file line number Diff line number Diff line change
@@ -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)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface ToolsRepository {
fun getToolsFlowByType(vararg types: Tool.Type) = getToolsFlowByType(types.toSet())
fun getToolsFlowByType(types: Collection<Tool.Type>): Flow<List<Tool>>
fun getToolsFlowForLanguage(locale: Locale): Flow<List<Tool>>
fun getDownloadedToolsFlowByTypesAndLanguage(types: Collection<Tool.Type>, locale: Locale): Flow<List<Tool>>
fun getMetaToolsFlow() = getToolsFlowByType(Tool.Type.META)
fun getLessonsFlow() = getToolsFlowByType(Tool.Type.LESSON)
fun getNormalToolsFlow() = getToolsFlowByType(Tool.Type.NORMAL_TYPES)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Tool.Type>, locale: Locale): Flow<List<ToolEntity>>
@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<Tool.Type>, locale: Locale): Flow<List<ToolEntity>>
@Query("SELECT * FROM tools")
suspend fun getToolFavorites(): List<ToolFavorite>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Tool.Type>, locale: Locale) =
dao.getDownloadedToolsFlowByTypeAndLanguage(types, locale).map { it.map { it.toModel() } }

override fun toolsChangeFlow(): Flow<Any?> = db.changeFlow("tools")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 99bd468

Please sign in to comment.