Compare commits
4 Commits
4b572f8773
...
9600ef84ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9600ef84ae | ||
|
|
c81e0886b8 | ||
|
|
9db538bf0a | ||
|
|
4cd014957f |
@@ -253,19 +253,23 @@ fun TranslatorApp(
|
|||||||
val currentDestination = navBackStackEntry?.destination
|
val currentDestination = navBackStackEntry?.destination
|
||||||
val selectedScreen = Screen.fromDestination(currentDestination)
|
val selectedScreen = Screen.fromDestination(currentDestination)
|
||||||
|
|
||||||
@Suppress("HardCodedStringLiteral") val isBottomBarHidden = currentDestination?.hierarchy?.any { destination ->
|
@Suppress("HardCodedStringLiteral")
|
||||||
|
val currentRoute = currentDestination?.route
|
||||||
|
val isHiddenByHierarchy = currentDestination?.hierarchy?.any { destination ->
|
||||||
destination.route in setOf(
|
destination.route in setOf(
|
||||||
Screen.Translation.route,
|
Screen.Translation.route,
|
||||||
Screen.Dictionary.route,
|
Screen.Dictionary.route,
|
||||||
Screen.Exercises.route,
|
Screen.Exercises.route,
|
||||||
Screen.Settings.route
|
Screen.Settings.route
|
||||||
)
|
)
|
||||||
} == true || currentDestination?.route in setOf(
|
} == true
|
||||||
"start_exercise",
|
val isBottomBarHidden = isHiddenByHierarchy || currentRoute in setOf(
|
||||||
"new_word",
|
"new_word",
|
||||||
"new_word_review",
|
"new_word_review",
|
||||||
"vocabulary_detail/{itemId}"
|
"vocabulary_detail/{itemId}",
|
||||||
)
|
"daily_review"
|
||||||
|
) || currentRoute?.startsWith("start_exercise") == true
|
||||||
|
|| currentRoute?.startsWith("vocabulary_exercise") == true
|
||||||
val showBottomNavLabels by settingsViewModel.showBottomNavLabels.collectAsStateWithLifecycle(initialValue = false)
|
val showBottomNavLabels by settingsViewModel.showBottomNavLabels.collectAsStateWithLifecycle(initialValue = false)
|
||||||
|
|
||||||
BottomNavigationBar(
|
BottomNavigationBar(
|
||||||
@@ -440,4 +444,4 @@ private fun AppTheme(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ import eu.gaudian.translator.view.exercises.MainExerciseScreen
|
|||||||
import eu.gaudian.translator.view.exercises.StartExerciseScreen
|
import eu.gaudian.translator.view.exercises.StartExerciseScreen
|
||||||
import eu.gaudian.translator.view.exercises.YouTubeBrowserScreen
|
import eu.gaudian.translator.view.exercises.YouTubeBrowserScreen
|
||||||
import eu.gaudian.translator.view.exercises.YouTubeExerciseScreen
|
import eu.gaudian.translator.view.exercises.YouTubeExerciseScreen
|
||||||
|
import eu.gaudian.translator.view.home.DailyReviewScreen
|
||||||
import eu.gaudian.translator.view.home.HomeScreen
|
import eu.gaudian.translator.view.home.HomeScreen
|
||||||
import eu.gaudian.translator.view.library.LibraryScreen
|
import eu.gaudian.translator.view.library.LibraryScreen
|
||||||
import eu.gaudian.translator.view.settings.DictionaryOptionsScreen
|
import eu.gaudian.translator.view.settings.DictionaryOptionsScreen
|
||||||
import eu.gaudian.translator.view.settings.SettingsRoutes
|
|
||||||
import eu.gaudian.translator.view.settings.TranslationSettingsScreen
|
import eu.gaudian.translator.view.settings.TranslationSettingsScreen
|
||||||
import eu.gaudian.translator.view.settings.settingsGraph
|
import eu.gaudian.translator.view.settings.settingsGraph
|
||||||
import eu.gaudian.translator.view.stats.StatsScreen
|
import eu.gaudian.translator.view.stats.StatsScreen
|
||||||
@@ -53,10 +53,12 @@ import eu.gaudian.translator.view.vocabulary.VocabularySortingScreen
|
|||||||
private const val TRANSITION_DURATION = 300
|
private const val TRANSITION_DURATION = 300
|
||||||
|
|
||||||
object NavigationRoutes {
|
object NavigationRoutes {
|
||||||
|
const val DAILY_REVIEW = "daily_review"
|
||||||
const val NEW_WORD = "new_word"
|
const val NEW_WORD = "new_word"
|
||||||
const val NEW_WORD_REVIEW = "new_word_review"
|
const val NEW_WORD_REVIEW = "new_word_review"
|
||||||
const val VOCABULARY_DETAIL = "vocabulary_detail"
|
const val VOCABULARY_DETAIL = "vocabulary_detail"
|
||||||
const val START_EXERCISE = "start_exercise"
|
const val START_EXERCISE = "start_exercise"
|
||||||
|
const val START_EXERCISE_DAILY = "start_exercise_daily"
|
||||||
const val CATEGORY_DETAIL = "category_detail"
|
const val CATEGORY_DETAIL = "category_detail"
|
||||||
const val CATEGORY_LIST = "category_list_screen"
|
const val CATEGORY_LIST = "category_list_screen"
|
||||||
const val STATS_VOCABULARY_HEATMAP = "stats/vocabulary_heatmap"
|
const val STATS_VOCABULARY_HEATMAP = "stats/vocabulary_heatmap"
|
||||||
@@ -68,6 +70,7 @@ object NavigationRoutes {
|
|||||||
const val STATS_VOCABULARY_LIST = "stats/vocabulary_list"
|
const val STATS_VOCABULARY_LIST = "stats/vocabulary_list"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(androidx.compose.animation.ExperimentalAnimationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavHost(
|
fun AppNavHost(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
@@ -81,15 +84,23 @@ fun AppNavHost(
|
|||||||
Screen.Home.route,
|
Screen.Home.route,
|
||||||
Screen.Library.route,
|
Screen.Library.route,
|
||||||
Screen.Stats.route,
|
Screen.Stats.route,
|
||||||
Screen.Translation.route,
|
|
||||||
Screen.Dictionary.route,
|
|
||||||
Screen.Exercises.route,
|
|
||||||
SettingsRoutes.LIST
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper to check if a route is a top-level tab
|
// Helper to check if a route is a top-level tab
|
||||||
|
// Note: Routes can be "main_home", "main_library" etc. but mainTabRoutes contains parent routes
|
||||||
fun isTabTransition(initial: String?, target: String?): Boolean {
|
fun isTabTransition(initial: String?, target: String?): Boolean {
|
||||||
return mainTabRoutes.contains(initial) && mainTabRoutes.contains(target)
|
if (initial == null || target == null) return false
|
||||||
|
// Check if either the direct route OR a "main_*" variant is in mainTabRoutes
|
||||||
|
val initialIsTab = mainTabRoutes.contains(initial) ||
|
||||||
|
mainTabRoutes.any { route ->
|
||||||
|
initial == "main_${route}" || initial.startsWith("${route}_")
|
||||||
|
}
|
||||||
|
val targetIsTab = mainTabRoutes.contains(target) ||
|
||||||
|
mainTabRoutes.any { route ->
|
||||||
|
target == "main_${route}" || target.startsWith("${route}_")
|
||||||
|
}
|
||||||
|
return initialIsTab && targetIsTab
|
||||||
}
|
}
|
||||||
|
|
||||||
NavHost(
|
NavHost(
|
||||||
@@ -146,6 +157,10 @@ fun AppNavHost(
|
|||||||
HomeScreen(navController = navController)
|
HomeScreen(navController = navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(NavigationRoutes.DAILY_REVIEW) {
|
||||||
|
DailyReviewScreen(navController = navController)
|
||||||
|
}
|
||||||
|
|
||||||
composable(NavigationRoutes.NEW_WORD) {
|
composable(NavigationRoutes.NEW_WORD) {
|
||||||
NewWordScreen(navController = navController)
|
NewWordScreen(navController = navController)
|
||||||
}
|
}
|
||||||
@@ -168,7 +183,16 @@ fun AppNavHost(
|
|||||||
val categoryId = categoryIdString?.toIntOrNull()
|
val categoryId = categoryIdString?.toIntOrNull()
|
||||||
StartExerciseScreen(
|
StartExerciseScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
preselectedCategoryId = categoryId
|
preselectedCategoryId = categoryId,
|
||||||
|
dueTodayOnly = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(NavigationRoutes.START_EXERCISE_DAILY) {
|
||||||
|
StartExerciseScreen(
|
||||||
|
navController = navController,
|
||||||
|
preselectedCategoryId = null,
|
||||||
|
dueTodayOnly = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -342,10 +342,12 @@ fun DictionarySimpleTopBar(
|
|||||||
languageName: String?,
|
languageName: String?,
|
||||||
onNavigateBack: () -> Unit
|
onNavigateBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
AppTopAppBar(
|
word?.let {
|
||||||
title = "TODO",
|
AppTopAppBar(
|
||||||
onNavigateBack = onNavigateBack
|
title = it,
|
||||||
)
|
onNavigateBack = onNavigateBack
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ fun EtymologyResultScreen(
|
|||||||
AppScaffold(
|
AppScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopAppBar(
|
AppTopAppBar(
|
||||||
title = "TODO",
|
title = "Result",
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
actions = {
|
actions = {
|
||||||
etymologyData?.let { data ->
|
etymologyData?.let { data ->
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import kotlinx.coroutines.launch
|
|||||||
fun StartExerciseScreen(
|
fun StartExerciseScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
preselectedCategoryId: Int? = null,
|
preselectedCategoryId: Int? = null,
|
||||||
|
dueTodayOnly: Boolean = false,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val activity = androidx.compose.ui.platform.LocalContext.current.findActivity()
|
val activity = androidx.compose.ui.platform.LocalContext.current.findActivity()
|
||||||
@@ -84,6 +85,15 @@ fun StartExerciseScreen(
|
|||||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val exerciseViewModel: VocabularyExerciseViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val exerciseViewModel: VocabularyExerciseViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
|
|
||||||
|
// Initialize exercise config with dueTodayOnly if specified
|
||||||
|
androidx.compose.runtime.LaunchedEffect(dueTodayOnly) {
|
||||||
|
if (dueTodayOnly) {
|
||||||
|
exerciseViewModel.updatePendingExerciseConfig(
|
||||||
|
exerciseViewModel.pendingExerciseConfig.value.copy(dueTodayOnly = true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val exerciseConfig by exerciseViewModel.pendingExerciseConfig.collectAsState()
|
val exerciseConfig by exerciseViewModel.pendingExerciseConfig.collectAsState()
|
||||||
val allCategories by categoryViewModel.categories.collectAsState(initial = emptyList())
|
val allCategories by categoryViewModel.categories.collectAsState(initial = emptyList())
|
||||||
var selectedLanguagePairs by remember { mutableStateOf<List<Pair<Language, Language>>>(emptyList()) }
|
var selectedLanguagePairs by remember { mutableStateOf<List<Pair<Language, Language>>>(emptyList()) }
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package eu.gaudian.translator.view.home
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import eu.gaudian.translator.R
|
||||||
|
import eu.gaudian.translator.utils.findActivity
|
||||||
|
import eu.gaudian.translator.view.NavigationRoutes
|
||||||
|
import eu.gaudian.translator.view.composable.AppButton
|
||||||
|
import eu.gaudian.translator.view.composable.AppScaffold
|
||||||
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
|
import eu.gaudian.translator.view.library.VocabularyCard
|
||||||
|
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||||
|
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DailyReviewScreen(
|
||||||
|
navController: NavHostController,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context.findActivity()
|
||||||
|
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
|
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
|
|
||||||
|
val dueTodayItems by vocabularyViewModel.dueTodayItems.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
|
val allLanguages by languageViewModel.allLanguages.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
AppScaffold(
|
||||||
|
topBar = {
|
||||||
|
AppTopAppBar(
|
||||||
|
title = stringResource(R.string.label_daily_review),
|
||||||
|
onNavigateBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = modifier.fillMaxSize()
|
||||||
|
) { paddingValues ->
|
||||||
|
if (dueTodayItems.isEmpty()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(32.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.size(200.dp),
|
||||||
|
painter = painterResource(id = R.drawable.ic_nothing_found),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_items_due_for_review),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
contentPadding = PaddingValues(16.dp)
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = dueTodayItems,
|
||||||
|
key = { it.id }
|
||||||
|
) { item ->
|
||||||
|
VocabularyCard(
|
||||||
|
item = item,
|
||||||
|
allLanguages = allLanguages,
|
||||||
|
isSelected = false,
|
||||||
|
onItemClick = {
|
||||||
|
vocabularyViewModel.setNavigationContext(dueTodayItems, item.id)
|
||||||
|
navController.navigate("${NavigationRoutes.VOCABULARY_DETAIL}/${item.id}")
|
||||||
|
},
|
||||||
|
onItemLongClick = { },
|
||||||
|
onDeleteClick = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Add spacing at the bottom for the button
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(80.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start Exercise Button (fixed at bottom)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(24.dp)
|
||||||
|
) {
|
||||||
|
AppButton(
|
||||||
|
onClick = {
|
||||||
|
navController.navigate(NavigationRoutes.START_EXERCISE_DAILY)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp),
|
||||||
|
enabled = dueTodayItems.isNotEmpty(),
|
||||||
|
shape = RoundedCornerShape(28.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.label_start_exercise_2d, dueTodayItems.size),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.PlayArrow,
|
||||||
|
contentDescription = stringResource(R.string.cd_play),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,7 @@ fun HomeScreen(
|
|||||||
val streak by viewModel.streak.collectAsState()
|
val streak by viewModel.streak.collectAsState()
|
||||||
val dailyGoal by viewModel.dailyGoal.collectAsState()
|
val dailyGoal by viewModel.dailyGoal.collectAsState()
|
||||||
val todayCompletedCount by viewModel.todayCompletedCount.collectAsState()
|
val todayCompletedCount by viewModel.todayCompletedCount.collectAsState()
|
||||||
|
val dueTodayCount by viewModel.dueTodayCount.collectAsState()
|
||||||
|
|
||||||
// Calculate daily goal progress
|
// Calculate daily goal progress
|
||||||
val progress = if (dailyGoal > 0) {
|
val progress = if (dailyGoal > 0) {
|
||||||
@@ -95,13 +96,12 @@ fun HomeScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
//TODO replace with actual implementation
|
|
||||||
@Suppress("HardCodedStringLiteral")
|
|
||||||
ActionCard(
|
ActionCard(
|
||||||
title = "Daily Review",
|
title = stringResource(R.string.label_daily_review),
|
||||||
subtitle = "42 words need attention",
|
subtitle = stringResource(R.string.desc_daily_review_due, dueTodayCount),
|
||||||
icon = Icons.Default.Psychology,
|
icon = Icons.Default.Psychology,
|
||||||
contentColor = MaterialTheme.colorScheme.onPrimary
|
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
onClick = { navController.navigate(NavigationRoutes.DAILY_REVIEW) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
@@ -146,6 +146,9 @@ fun TopProfileSection(navController: NavHostController, context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { navController.navigate(Screen.Settings.route) },
|
onClick = { navController.navigate(Screen.Settings.route) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -158,6 +161,7 @@ fun TopProfileSection(navController: NavHostController, context: Context) {
|
|||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package eu.gaudian.translator.view.vocabulary
|
package eu.gaudian.translator.view.vocabulary
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -14,9 +13,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardDefaults
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@@ -50,6 +46,7 @@ import eu.gaudian.translator.model.TagCategory
|
|||||||
import eu.gaudian.translator.model.VocabularyFilter
|
import eu.gaudian.translator.model.VocabularyFilter
|
||||||
import eu.gaudian.translator.model.VocabularyItem
|
import eu.gaudian.translator.model.VocabularyItem
|
||||||
import eu.gaudian.translator.utils.findActivity
|
import eu.gaudian.translator.utils.findActivity
|
||||||
|
import eu.gaudian.translator.view.composable.AppCard
|
||||||
import eu.gaudian.translator.view.composable.AppIcons
|
import eu.gaudian.translator.view.composable.AppIcons
|
||||||
import eu.gaudian.translator.view.composable.AppScaffold
|
import eu.gaudian.translator.view.composable.AppScaffold
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
@@ -58,6 +55,7 @@ import eu.gaudian.translator.view.dialogs.DeleteCategoryDialog
|
|||||||
import eu.gaudian.translator.view.dialogs.DeleteItemsDialog
|
import eu.gaudian.translator.view.dialogs.DeleteItemsDialog
|
||||||
import eu.gaudian.translator.view.dialogs.EditCategoryDialog
|
import eu.gaudian.translator.view.dialogs.EditCategoryDialog
|
||||||
import eu.gaudian.translator.view.vocabulary.widgets.CategoryProgressCircle
|
import eu.gaudian.translator.view.vocabulary.widgets.CategoryProgressCircle
|
||||||
|
import eu.gaudian.translator.view.vocabulary.widgets.ChartLegend
|
||||||
import eu.gaudian.translator.viewmodel.CategoryProgress
|
import eu.gaudian.translator.viewmodel.CategoryProgress
|
||||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||||
import eu.gaudian.translator.viewmodel.ExportImportViewModel
|
import eu.gaudian.translator.viewmodel.ExportImportViewModel
|
||||||
@@ -313,16 +311,10 @@ fun CategoryHeaderCard(
|
|||||||
onDeleteClick: () -> Unit,
|
onDeleteClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Card(
|
AppCard(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 16.dp),
|
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||||
shape = RoundedCornerShape(20.dp),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.2f)
|
|
||||||
),
|
|
||||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.4f)),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -343,6 +335,8 @@ fun CategoryHeaderCard(
|
|||||||
|
|
||||||
// Progress Circle - smaller size
|
// Progress Circle - smaller size
|
||||||
if (categoryProgress != null) {
|
if (categoryProgress != null) {
|
||||||
|
|
||||||
|
|
||||||
CategoryProgressCircle(
|
CategoryProgressCircle(
|
||||||
totalItems = categoryProgress.totalItems,
|
totalItems = categoryProgress.totalItems,
|
||||||
itemsCompleted = categoryProgress.itemsCompleted,
|
itemsCompleted = categoryProgress.itemsCompleted,
|
||||||
@@ -350,7 +344,9 @@ fun CategoryHeaderCard(
|
|||||||
newItems = categoryProgress.newItems,
|
newItems = categoryProgress.newItems,
|
||||||
circleSize = 100.dp,
|
circleSize = 100.dp,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
ChartLegend()
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action Buttons
|
// Action Buttons
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -85,7 +84,6 @@ fun VocabularyCardHost(
|
|||||||
AppScaffold(
|
AppScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopAppBar(
|
AppTopAppBar(
|
||||||
modifier = Modifier.height(56.dp),
|
|
||||||
title = stringResource(R.string.item_details),
|
title = stringResource(R.string.item_details),
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
actions = {
|
actions = {
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ fun CategoryProgressCircle(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChartLegend() {
|
fun ChartLegend() {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ fun WeeklyActivityChartWidget(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
) {
|
) {
|
||||||
ChartLegend()
|
WeeklyChartLegend()
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -164,7 +164,7 @@ private fun RowScope.Bar(value: Int, maxValue: Int, color: Color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChartLegend() {
|
private fun WeeklyChartLegend() {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -450,6 +450,7 @@
|
|||||||
<string name="no_models_configured">No Models Configured</string>
|
<string name="no_models_configured">No Models Configured</string>
|
||||||
<string name="no_models_found">No models found</string>
|
<string name="no_models_found">No models found</string>
|
||||||
<string name="no_new_vocabulary_to_sort">No New Vocabulary to Sort</string>
|
<string name="no_new_vocabulary_to_sort">No New Vocabulary to Sort</string>
|
||||||
|
<string name="no_items_due_for_review">No items due for review today. Great job!</string>
|
||||||
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">No vocabulary items found. Perhaps try changing the filters?</string>
|
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">No vocabulary items found. Perhaps try changing the filters?</string>
|
||||||
|
|
||||||
<string name="not_available">Not available</string>
|
<string name="not_available">Not available</string>
|
||||||
@@ -1088,6 +1089,9 @@
|
|||||||
<string name="label_2d_days">%1$d Days</string>
|
<string name="label_2d_days">%1$d Days</string>
|
||||||
<string name="label_current_streak">Current Streak</string>
|
<string name="label_current_streak">Current Streak</string>
|
||||||
<string name="label_daily_goal">Daily Goal</string>
|
<string name="label_daily_goal">Daily Goal</string>
|
||||||
|
<string name="label_daily_review">Daily Review</string>
|
||||||
|
<string name="desc_daily_review_due">%1$d words need attention</string>
|
||||||
|
<string name="text_daily_review_placeholder">Daily review screen - implementation pending</string>
|
||||||
<string name="text_desc_no_activity_data_available">No activity data available</string>
|
<string name="text_desc_no_activity_data_available">No activity data available</string>
|
||||||
<string name="label_see_history">See History</string>
|
<string name="label_see_history">See History</string>
|
||||||
<string name="label_weekly_progress">Weekly Progress</string>
|
<string name="label_weekly_progress">Weekly Progress</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user