implement CEFR level filtering and language-based sorting in ExplorePacksScreen
This commit is contained in:
@@ -3,6 +3,9 @@ package eu.gaudian.translator.model.communication
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import eu.gaudian.translator.R
|
import eu.gaudian.translator.R
|
||||||
|
import eu.gaudian.translator.model.communication.files_download.FlashcardCollectionInfo
|
||||||
|
import eu.gaudian.translator.model.communication.files_download.FlashcardManifestResponse
|
||||||
|
import eu.gaudian.translator.model.communication.files_download.VocabCollectionInfo
|
||||||
import eu.gaudian.translator.utils.Log
|
import eu.gaudian.translator.utils.Log
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -327,7 +330,7 @@ class FileDownloadManager(private val context: Context) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the vocabulary-pack manifest (vocab_manifest.json).
|
* Fetches the vocabulary-pack manifest (vocab_manifest.json).
|
||||||
* Unwraps the top-level [VocabManifestResponse] and returns the `lists` array.
|
* Unwraps the top-level [eu.gaudian.translator.model.communication.files_download.VocabManifestResponse] and returns the `lists` array.
|
||||||
*/
|
*/
|
||||||
suspend fun fetchVocabManifest(): List<VocabCollectionInfo>? = withContext(Dispatchers.IO) {
|
suspend fun fetchVocabManifest(): List<VocabCollectionInfo>? = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package eu.gaudian.translator.model.communication
|
package eu.gaudian.translator.model.communication
|
||||||
|
|
||||||
|
import eu.gaudian.translator.model.communication.files_download.FlashcardManifestResponse
|
||||||
|
import eu.gaudian.translator.model.communication.files_download.VocabManifestResponse
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package eu.gaudian.translator.model.communication
|
package eu.gaudian.translator.model.communication.files_download
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
@@ -35,6 +35,8 @@ data class VocabCollectionInfo(
|
|||||||
@SerializedName("item_count") val itemCount: Int,
|
@SerializedName("item_count") val itemCount: Int,
|
||||||
@SerializedName("emoji") val emoji: String,
|
@SerializedName("emoji") val emoji: String,
|
||||||
@SerializedName("version") val version: Int,
|
@SerializedName("version") val version: Int,
|
||||||
|
/** CEFR difficulty level: A1, A2, B1, B2, C1, C2 (empty string if not set) */
|
||||||
|
@SerializedName("level") val level: String = "",
|
||||||
@SerializedName("size_bytes") val sizeBytes: Long,
|
@SerializedName("size_bytes") val sizeBytes: Long,
|
||||||
@SerializedName("checksum_sha256") val checksumSha256: String,
|
@SerializedName("checksum_sha256") val checksumSha256: String,
|
||||||
@SerializedName("created_at") val createdAt: String,
|
@SerializedName("created_at") val createdAt: String,
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
@@ -28,7 +30,6 @@ import androidx.compose.material.icons.filled.Delete
|
|||||||
import androidx.compose.material.icons.filled.FilterList
|
import androidx.compose.material.icons.filled.FilterList
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.SwapHoriz
|
|
||||||
import androidx.compose.material.icons.filled.Warning
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
@@ -75,8 +76,10 @@ import eu.gaudian.translator.utils.StatusMessageId
|
|||||||
import eu.gaudian.translator.utils.StatusMessageService
|
import eu.gaudian.translator.utils.StatusMessageService
|
||||||
import eu.gaudian.translator.utils.findActivity
|
import eu.gaudian.translator.utils.findActivity
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
|
import eu.gaudian.translator.view.translation.LanguageSelectorBar
|
||||||
import eu.gaudian.translator.viewmodel.ExportImportViewModel
|
import eu.gaudian.translator.viewmodel.ExportImportViewModel
|
||||||
import eu.gaudian.translator.viewmodel.ImportState
|
import eu.gaudian.translator.viewmodel.ImportState
|
||||||
|
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||||
import eu.gaudian.translator.viewmodel.PackDownloadState
|
import eu.gaudian.translator.viewmodel.PackDownloadState
|
||||||
import eu.gaudian.translator.viewmodel.PackUiState
|
import eu.gaudian.translator.viewmodel.PackUiState
|
||||||
import eu.gaudian.translator.viewmodel.VocabPacksViewModel
|
import eu.gaudian.translator.viewmodel.VocabPacksViewModel
|
||||||
@@ -84,18 +87,31 @@ import eu.gaudian.translator.viewmodel.VocabPacksViewModel
|
|||||||
private const val TAG = "ExplorePacksScreen"
|
private const val TAG = "ExplorePacksScreen"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Filter enum
|
// Filter enum – CEFR levels + utility filters
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
enum class PackFilter { All, MostPopular, Newest, Beginner }
|
enum class PackFilter {
|
||||||
|
All, Newest,
|
||||||
|
A1, A2, B1, B2, C1, C2;
|
||||||
|
|
||||||
private val PackFilter.label: String
|
val label: String
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
PackFilter.All -> "All"
|
All -> "All"
|
||||||
PackFilter.MostPopular -> "Most Popular"
|
Newest -> "Newest"
|
||||||
PackFilter.Newest -> "Newest"
|
A1 -> "Beginner · A1"
|
||||||
PackFilter.Beginner -> "Beginner"
|
A2 -> "Elementary · A2"
|
||||||
}
|
B1 -> "Intermediate · B1"
|
||||||
|
B2 -> "Upper Int. · B2"
|
||||||
|
C1 -> "Advanced · C1"
|
||||||
|
C2 -> "Proficient · C2"
|
||||||
|
}
|
||||||
|
|
||||||
|
val cefrCode: String?
|
||||||
|
get() = when (this) {
|
||||||
|
A1 -> "A1"; A2 -> "A2"; B1 -> "B1"; B2 -> "B2"; C1 -> "C1"; C2 -> "C2"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Gradient palette – deterministic per pack ID hash
|
// Gradient palette – deterministic per pack ID hash
|
||||||
@@ -127,26 +143,26 @@ fun ExplorePacksScreen(
|
|||||||
) {
|
) {
|
||||||
val activity = LocalContext.current.findActivity()
|
val activity = LocalContext.current.findActivity()
|
||||||
|
|
||||||
// VocabPacksViewModel is screen-scoped (not activity-scoped) – no persistent pack state needed
|
val vocabPacksViewModel: VocabPacksViewModel = hiltViewModel()
|
||||||
val vocabPacksViewModel: VocabPacksViewModel = hiltViewModel()
|
val exportImportViewModel: ExportImportViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
// ExportImportViewModel handles full Polly-format import with real conflict strategy
|
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val exportImportViewModel: ExportImportViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
|
|
||||||
val packs by vocabPacksViewModel.packs.collectAsState()
|
val packs by vocabPacksViewModel.packs.collectAsState()
|
||||||
val isLoadingManifest by vocabPacksViewModel.isLoadingManifest.collectAsState()
|
val isLoadingManifest by vocabPacksViewModel.isLoadingManifest.collectAsState()
|
||||||
val manifestError by vocabPacksViewModel.manifestError.collectAsState()
|
val manifestError by vocabPacksViewModel.manifestError.collectAsState()
|
||||||
val importState by exportImportViewModel.importState.collectAsState()
|
val importState by exportImportViewModel.importState.collectAsState()
|
||||||
|
val selectedSourceLanguage by languageViewModel.selectedSourceLanguage.collectAsState()
|
||||||
|
val selectedTargetLanguage by languageViewModel.selectedTargetLanguage.collectAsState()
|
||||||
|
|
||||||
var searchQuery by remember { mutableStateOf("") }
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
var selectedFilter by remember { mutableStateOf(PackFilter.All) }
|
var selectedFilter by remember { mutableStateOf(PackFilter.All) }
|
||||||
|
|
||||||
// Which pack is being imported right now (captured when dialog opens)
|
var pendingImportPackState by remember { mutableStateOf<PackUiState?>(null) }
|
||||||
var pendingImportPackState by remember { mutableStateOf<PackUiState?>(null) }
|
var selectedConflictStrategy by remember { mutableStateOf(ConflictStrategy.MERGE) }
|
||||||
var selectedConflictStrategy by remember { mutableStateOf(ConflictStrategy.MERGE) }
|
var showStrategyDialog by remember { mutableStateOf(false) }
|
||||||
var showStrategyDialog by remember { mutableStateOf(false) }
|
var isImporting by remember { mutableStateOf(false) }
|
||||||
var isImporting by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
// Observe importState and react when the async import finishes
|
// Observe async import result
|
||||||
LaunchedEffect(importState) {
|
LaunchedEffect(importState) {
|
||||||
val pending = pendingImportPackState ?: return@LaunchedEffect
|
val pending = pendingImportPackState ?: return@LaunchedEffect
|
||||||
when (val state = importState) {
|
when (val state = importState) {
|
||||||
@@ -165,23 +181,43 @@ fun ExplorePacksScreen(
|
|||||||
isImporting = false
|
isImporting = false
|
||||||
exportImportViewModel.resetImportState()
|
exportImportViewModel.resetImportState()
|
||||||
}
|
}
|
||||||
else -> { /* Idle or Loading – nothing to do */ }
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val filteredPacks = remember(packs, selectedFilter, searchQuery) {
|
// Filtered + sorted pack list
|
||||||
|
val filteredPacks = remember(
|
||||||
|
packs, selectedFilter, searchQuery,
|
||||||
|
selectedSourceLanguage, selectedTargetLanguage
|
||||||
|
) {
|
||||||
|
val srcId = selectedSourceLanguage?.nameResId
|
||||||
|
val tgtId = selectedTargetLanguage?.nameResId
|
||||||
|
|
||||||
packs.filter { ps ->
|
packs.filter { ps ->
|
||||||
val info = ps.info
|
val info = ps.info
|
||||||
|
|
||||||
|
// Language filter – only when a language pair is selected
|
||||||
|
val matchLanguage = if (srcId == null && tgtId == null) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
val ids = info.languageIds.toSet()
|
||||||
|
(srcId == null || ids.contains(srcId)) &&
|
||||||
|
(tgtId == null || ids.contains(tgtId))
|
||||||
|
}
|
||||||
|
|
||||||
val matchSearch = searchQuery.isBlank() ||
|
val matchSearch = searchQuery.isBlank() ||
|
||||||
info.name.contains(searchQuery, ignoreCase = true) ||
|
info.name.contains(searchQuery, ignoreCase = true) ||
|
||||||
info.category.contains(searchQuery, ignoreCase = true)
|
info.category.contains(searchQuery, ignoreCase = true)
|
||||||
val matchFilter = when (selectedFilter) {
|
|
||||||
PackFilter.All -> true
|
val matchFilter = when (val code = selectedFilter.cefrCode) {
|
||||||
PackFilter.MostPopular -> info.itemCount >= 80
|
null -> true // All or Newest – handled by sort below
|
||||||
PackFilter.Newest -> info.version >= 1
|
else -> info.level.equals(code, ignoreCase = true)
|
||||||
PackFilter.Beginner -> info.itemCount <= 60
|
|
||||||
}
|
}
|
||||||
matchSearch && matchFilter
|
|
||||||
|
matchLanguage && matchSearch && matchFilter
|
||||||
|
}.let { list ->
|
||||||
|
if (selectedFilter == PackFilter.Newest) list.sortedByDescending { it.info.version }
|
||||||
|
else list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,17 +275,27 @@ fun ExplorePacksScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
// ── Language selector row ─────────────────────────────────────────
|
// ── Language selector – reuses LanguageSelectorBar ────────────────
|
||||||
PackLanguageSelectorRow()
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
tonalElevation = 1.dp
|
||||||
|
) {
|
||||||
|
LanguageSelectorBar(
|
||||||
|
languageViewModel = languageViewModel,
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
// ── Filter chips ──────────────────────────────────────────────────
|
// ── Filter chips – horizontally scrollable ────────────────────────
|
||||||
Row(
|
LazyRow(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = Modifier.fillMaxWidth()
|
contentPadding = PaddingValues(horizontal = 2.dp)
|
||||||
) {
|
) {
|
||||||
PackFilter.entries.forEach { filter ->
|
items(PackFilter.entries) { filter ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = selectedFilter == filter,
|
selected = selectedFilter == filter,
|
||||||
onClick = { selectedFilter = filter },
|
onClick = { selectedFilter = filter },
|
||||||
@@ -375,9 +421,9 @@ fun ExplorePacksScreen(
|
|||||||
pendingImportPackState = null
|
pendingImportPackState = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = { Icon(Icons.Default.Warning, contentDescription = null) },
|
icon = { Icon(Icons.Default.Warning, contentDescription = null) },
|
||||||
title = { Text("Import ${packState.info.name} ") },
|
title = { Text("Import \"${packState.info.name}\"") },
|
||||||
text = {
|
text = {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
if (isImporting) {
|
if (isImporting) {
|
||||||
Box(
|
Box(
|
||||||
@@ -444,14 +490,13 @@ fun ExplorePacksScreen(
|
|||||||
pendingImportPackState = null
|
pendingImportPackState = null
|
||||||
return@TextButton
|
return@TextButton
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Starting import for ${packState.info.id} with strategy=$selectedConflictStrategy, json=${json.length} chars")
|
Log.d(TAG, "Starting import for ${packState.info.id} " +
|
||||||
|
"strategy=$selectedConflictStrategy json=${json.length} chars")
|
||||||
isImporting = true
|
isImporting = true
|
||||||
showStrategyDialog = false // close dialog; LaunchedEffect handles completion
|
showStrategyDialog = false
|
||||||
exportImportViewModel.importFromJson(json, selectedConflictStrategy)
|
exportImportViewModel.importFromJson(json, selectedConflictStrategy)
|
||||||
}
|
}
|
||||||
) {
|
) { Text("Add to Library") }
|
||||||
Text("Add to Library")
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -460,53 +505,14 @@ fun ExplorePacksScreen(
|
|||||||
showStrategyDialog = false
|
showStrategyDialog = false
|
||||||
pendingImportPackState = null
|
pendingImportPackState = null
|
||||||
}
|
}
|
||||||
) {
|
) { Text("Cancel") }
|
||||||
Text("Cancel")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Language selector (placeholder — will use SourceLanguageDropdown in future)
|
// Conflict strategy option
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun PackLanguageSelectorRow(modifier: Modifier = Modifier) {
|
|
||||||
Surface(
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
|
||||||
tonalElevation = 1.dp
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text("🇬🇧", fontSize = 18.sp)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text("English", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
|
||||||
}
|
|
||||||
IconButton(onClick = { /* TODO: swap */ }, modifier = Modifier.size(32.dp)) {
|
|
||||||
Icon(Icons.Default.SwapHoriz, contentDescription = "Swap",
|
|
||||||
tint = MaterialTheme.colorScheme.primary)
|
|
||||||
}
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text("Spanish", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text("🇪🇸", fontSize = 18.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Conflict strategy option (mirrors VocabularyRepositoryOptionsScreen)
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -527,9 +533,7 @@ private fun PackConflictStrategyOption(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(selected = selected, onClick = onSelected)
|
RadioButton(selected = selected, onClick = onSelected)
|
||||||
@@ -599,7 +603,25 @@ private fun PackCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status badge (top-right corner)
|
// Level badge (top-left) – only when a CEFR level is set
|
||||||
|
if (info.level.isNotBlank()) {
|
||||||
|
Box(modifier = Modifier.align(Alignment.TopStart).padding(8.dp)) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(6.dp),
|
||||||
|
color = Color.Black.copy(alpha = 0.45f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = info.level,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 3.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status badge (top-right)
|
||||||
val badgeData: Pair<Color, String>? = when (packState.downloadState) {
|
val badgeData: Pair<Color, String>? = when (packState.downloadState) {
|
||||||
PackDownloadState.DOWNLOADED -> Color(0xFF1565C0) to "Ready"
|
PackDownloadState.DOWNLOADED -> Color(0xFF1565C0) to "Ready"
|
||||||
PackDownloadState.IMPORTED -> Color(0xFF388E3C) to "In Library"
|
PackDownloadState.IMPORTED -> Color(0xFF388E3C) to "In Library"
|
||||||
@@ -671,8 +693,7 @@ private fun PackCard(
|
|||||||
contentPadding = PaddingValues(0.dp),
|
contentPadding = PaddingValues(0.dp),
|
||||||
shape = RoundedCornerShape(8.dp)
|
shape = RoundedCornerShape(8.dp)
|
||||||
) {
|
) {
|
||||||
Text("Get",
|
Text("Get", style = MaterialTheme.typography.labelMedium,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
fontWeight = FontWeight.Bold)
|
fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,11 +721,9 @@ private fun PackCard(
|
|||||||
containerColor = MaterialTheme.colorScheme.secondary
|
containerColor = MaterialTheme.colorScheme.secondary
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text("Add ${info.itemCount} words",
|
||||||
"Add ${info.itemCount} words",
|
style = MaterialTheme.typography.labelSmall,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
fontWeight = FontWeight.Bold)
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = onDeleteClick,
|
onClick = onDeleteClick,
|
||||||
@@ -745,8 +764,7 @@ private fun PackCard(
|
|||||||
containerColor = MaterialTheme.colorScheme.error
|
containerColor = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text("Retry",
|
Text("Retry", style = MaterialTheme.typography.labelMedium,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
fontWeight = FontWeight.Bold)
|
fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.gaudian.translator.model.communication.FileDownloadManager
|
import eu.gaudian.translator.model.communication.FileDownloadManager
|
||||||
import eu.gaudian.translator.model.communication.VocabCollectionInfo
|
import eu.gaudian.translator.model.communication.files_download.VocabCollectionInfo
|
||||||
import eu.gaudian.translator.utils.Log
|
import eu.gaudian.translator.utils.Log
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|||||||
Reference in New Issue
Block a user