ViewModel for dashboard
This commit is contained in:
@@ -57,6 +57,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.compose.material.icons.extended)
|
implementation(libs.androidx.compose.material.icons.extended)
|
||||||
implementation(libs.androidx.compose.material3.adaptive.navigation.suite)
|
implementation(libs.androidx.compose.material3.adaptive.navigation.suite)
|
||||||
implementation(libs.androidx.compose.material3.window.size.class1)
|
implementation(libs.androidx.compose.material3.window.size.class1)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ import androidx.compose.material3.CardDefaults
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import net.tinsae.clocked.components.ActivityList
|
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.ClockedTheme
|
||||||
import net.tinsae.clocked.ui.theme.cyan
|
import net.tinsae.clocked.ui.theme.cyan
|
||||||
import net.tinsae.clocked.ui.theme.green
|
import net.tinsae.clocked.ui.theme.green
|
||||||
import net.tinsae.clocked.ui.theme.red
|
import net.tinsae.clocked.ui.theme.red
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DashboardScreen(modifier: Modifier = Modifier) {
|
fun DashboardScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: DashboardViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -47,17 +47,15 @@ fun DashboardScreen(modifier: Modifier = Modifier) {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
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))
|
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))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
NetBalanceCard(value = "+6h 30m")
|
NetBalanceCard(value = uiState.netBalance)
|
||||||
|
|
||||||
// ADDED: Manual spacer
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -69,13 +67,10 @@ fun DashboardScreen(modifier: Modifier = Modifier) {
|
|||||||
ActionButton(text = "− Time Off", modifier = Modifier.weight(1f))
|
ActionButton(text = "− Time Off", modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADDED: Manual spacer
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
// This section now works as expected
|
|
||||||
val recentLogs by remember { mutableStateOf(getRecentLogs()) }
|
|
||||||
ActivityList(
|
ActivityList(
|
||||||
items = recentLogs,
|
items = uiState.recentActivities,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -131,6 +126,6 @@ fun ActionButton(text: String, modifier: Modifier = Modifier) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun DashboardScreenPreview() {
|
fun DashboardScreenPreview() {
|
||||||
ClockedTheme {
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
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.getOvertimeLogs
|
||||||
import net.tinsae.clocked.service.getTimeOffLogs
|
import net.tinsae.clocked.service.getTimeOffLogs
|
||||||
import net.tinsae.clocked.ui.theme.ClockedTheme
|
import net.tinsae.clocked.ui.theme.ClockedTheme
|
||||||
import java.time.Instant
|
import net.tinsae.clocked.util.Util.formatTimestampToMonthYear
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -69,6 +69,20 @@ fun getRecentLogs(): List<Log> {
|
|||||||
return logs.take(7)
|
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 {
|
fun deleteLog(log: Log): Boolean {
|
||||||
logs.filter { it.id != log.id }
|
logs.filter { it.id != log.id }
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ espressoCore = "3.7.0"
|
|||||||
lifecycleRuntimeKtx = "2.10.0"
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
activityCompose = "1.12.2"
|
activityCompose = "1.12.2"
|
||||||
composeBom = "2025.12.01"
|
composeBom = "2025.12.01"
|
||||||
|
lifecycle = "2.8.0"
|
||||||
|
|
||||||
desugarJdkLibs = "2.1.5"
|
desugarJdkLibs = "2.1.5"
|
||||||
material3WindowSizeClass = "1.4.0"
|
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-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
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-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-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-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
|
|||||||
Reference in New Issue
Block a user