ViewModel for dashboard
This commit is contained in:
@@ -57,6 +57,7 @@ dependencies {
|
||||
implementation(libs.androidx.compose.material.icons.extended)
|
||||
implementation(libs.androidx.compose.material3.adaptive.navigation.suite)
|
||||
implementation(libs.androidx.compose.material3.window.size.class1)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
@@ -15,9 +15,8 @@ import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -25,18 +24,19 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import net.tinsae.clocked.components.ActivityList
|
||||
import net.tinsae.clocked.service.deleteLog
|
||||
import net.tinsae.clocked.service.editLog
|
||||
import net.tinsae.clocked.service.getRecentLogs
|
||||
import net.tinsae.clocked.ui.theme.ClockedTheme
|
||||
import net.tinsae.clocked.ui.theme.cyan
|
||||
import net.tinsae.clocked.ui.theme.green
|
||||
import net.tinsae.clocked.ui.theme.red
|
||||
|
||||
@Composable
|
||||
fun DashboardScreen(modifier: Modifier = Modifier) {
|
||||
|
||||
fun DashboardScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: DashboardViewModel = viewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
@@ -47,17 +47,15 @@ fun DashboardScreen(modifier: Modifier = Modifier) {
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
SummaryCard(title = "Overtime", value = "+12h 30m", color = green, modifier = Modifier.weight(1f))
|
||||
SummaryCard(title = "Overtime", value = uiState.overtime, color = green, modifier = Modifier.weight(1f))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
SummaryCard(title = "Time Off", value = "−6h 00m", color = red, modifier = Modifier.weight(1f))
|
||||
SummaryCard(title = "Time Off", value = uiState.timeOff, color = red, modifier = Modifier.weight(1f))
|
||||
}
|
||||
|
||||
// ADDED: Manual spacer
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
NetBalanceCard(value = "+6h 30m")
|
||||
NetBalanceCard(value = uiState.netBalance)
|
||||
|
||||
// ADDED: Manual spacer
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
@@ -69,13 +67,10 @@ fun DashboardScreen(modifier: Modifier = Modifier) {
|
||||
ActionButton(text = "− Time Off", modifier = Modifier.weight(1f))
|
||||
}
|
||||
|
||||
// ADDED: Manual spacer
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// This section now works as expected
|
||||
val recentLogs by remember { mutableStateOf(getRecentLogs()) }
|
||||
ActivityList(
|
||||
items = recentLogs,
|
||||
items = uiState.recentActivities,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
@@ -131,6 +126,6 @@ fun ActionButton(text: String, modifier: Modifier = Modifier) {
|
||||
@Composable
|
||||
fun DashboardScreenPreview() {
|
||||
ClockedTheme {
|
||||
DashboardScreen()
|
||||
DashboardScreen(viewModel = DashboardViewModel())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.tinsae.clocked.dashboard
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import net.tinsae.clocked.data.Log
|
||||
import net.tinsae.clocked.service.getRecentLogs
|
||||
import net.tinsae.clocked.service.getTotalOvertimeDuration
|
||||
import net.tinsae.clocked.service.getTotalTimeOffDuration
|
||||
import net.tinsae.clocked.util.Util.formatDuration
|
||||
|
||||
data class DashboardUiState(
|
||||
val overtime: String = "0m",
|
||||
val timeOff: String = "0m",
|
||||
val netBalance: String = "0m",
|
||||
val recentActivities: List<Log> = emptyList()
|
||||
)
|
||||
|
||||
class DashboardViewModel : ViewModel() {
|
||||
private val _uiState = MutableStateFlow(DashboardUiState())
|
||||
val uiState: StateFlow<DashboardUiState> = _uiState
|
||||
|
||||
init {
|
||||
loadDashboardData()
|
||||
}
|
||||
|
||||
private fun loadDashboardData() {
|
||||
val recentLogs = getRecentLogs()
|
||||
|
||||
val overtimeDuration = getTotalOvertimeDuration()
|
||||
val timeOffDuration = getTotalTimeOffDuration()
|
||||
|
||||
// timeOffDuration is negative, so we add it to get the difference
|
||||
val netBalanceDuration = overtimeDuration.plus(timeOffDuration)
|
||||
|
||||
_uiState.value = DashboardUiState(
|
||||
overtime = formatDuration(overtimeDuration),
|
||||
timeOff = formatDuration(timeOffDuration),
|
||||
netBalance = formatDuration(netBalanceDuration),
|
||||
recentActivities = recentLogs
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,12 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -35,22 +32,7 @@ import net.tinsae.clocked.service.getAllLogs
|
||||
import net.tinsae.clocked.service.getOvertimeLogs
|
||||
import net.tinsae.clocked.service.getTimeOffLogs
|
||||
import net.tinsae.clocked.ui.theme.ClockedTheme
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
// Helper to format the Instant into a readable string like "Tue 18"
|
||||
fun formatTimestampToDayAndDate(instant: Instant): String {
|
||||
val formatter = DateTimeFormatter.ofPattern("E d").withZone(ZoneId.systemDefault())
|
||||
return formatter.format(instant)
|
||||
}
|
||||
|
||||
// Helper to format the Instant into a month and year like "March 2025"
|
||||
fun formatTimestampToMonthYear(instant: Instant): String {
|
||||
val formatter = DateTimeFormatter.ofPattern("MMMM yyyy").withZone(ZoneId.systemDefault())
|
||||
return formatter.format(instant)
|
||||
}
|
||||
|
||||
import net.tinsae.clocked.util.Util.formatTimestampToMonthYear
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
|
||||
@@ -69,6 +69,20 @@ fun getRecentLogs(): List<Log> {
|
||||
return logs.take(7)
|
||||
}
|
||||
|
||||
fun getTotalOvertimeDuration(): Duration {
|
||||
return logs
|
||||
.filter { it.type == EntryType.OVERTIME }
|
||||
.map { it.duration }
|
||||
.fold(Duration.ZERO, Duration::plus)
|
||||
}
|
||||
|
||||
fun getTotalTimeOffDuration(): Duration {
|
||||
return logs
|
||||
.filter { it.type == EntryType.TIME_OFF }
|
||||
.map { it.duration }
|
||||
.fold(Duration.ZERO, Duration::plus)
|
||||
}
|
||||
|
||||
|
||||
fun deleteLog(log: Log): Boolean {
|
||||
logs.filter { it.id != log.id }
|
||||
|
||||
@@ -8,6 +8,7 @@ espressoCore = "3.7.0"
|
||||
lifecycleRuntimeKtx = "2.10.0"
|
||||
activityCompose = "1.12.2"
|
||||
composeBom = "2025.12.01"
|
||||
lifecycle = "2.8.0"
|
||||
|
||||
desugarJdkLibs = "2.1.5"
|
||||
material3WindowSizeClass = "1.4.0"
|
||||
@@ -21,6 +22,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
|
||||
Reference in New Issue
Block a user