Merge pull request #66 from SELab1/timers

Timers
This commit is contained in:
lbarraga 2023-04-17 15:52:59 +02:00 committed by GitHub Enterprise
commit f963256ec2
29 changed files with 699 additions and 38 deletions

View file

@ -20,10 +20,12 @@ import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.home.HomeScreen
import be.ugent.sel.studeez.screens.log_in.LoginScreen
import be.ugent.sel.studeez.screens.session.SessionScreen
import be.ugent.sel.studeez.screens.profile.EditProfileScreen
import be.ugent.sel.studeez.screens.profile.ProfileScreen
import be.ugent.sel.studeez.screens.sign_up.SignUpScreen
import be.ugent.sel.studeez.screens.splash.SplashScreen
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewScreen
import be.ugent.sel.studeez.ui.theme.StudeezTheme
import kotlinx.coroutines.CoroutineScope
@ -113,6 +115,14 @@ fun NavGraphBuilder.studeezGraph(appState: StudeezAppstate) {
ProfileScreen(open, openAndPopUp)
}
composable(StudeezDestinations.TIMER_OVERVIEW_SCREEN) {
TimerOverviewScreen(openAndPopUp)
}
composable(StudeezDestinations.SESSION_SCREEN) {
SessionScreen(openAndPopUp)
}
// TODO Timers screen
// TODO Settings screen

View file

@ -0,0 +1,16 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
class FunctionalCustomTimer(studyTime: Int): FunctionalTimer(studyTime) {
override fun tick() {
if (time.getTime() == 0) {
view = "Done!"
} else {
time.minOne()
}
}
override fun hasEnded(): Boolean {
return time.getTime() == 0
}
}

View file

@ -0,0 +1,12 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
class FunctionalEndlessTimer() : FunctionalTimer(0){
override fun hasEnded(): Boolean {
return false
}
override fun tick() {
time.plusOne()
}
}

View file

@ -0,0 +1,34 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
class FunctionalPomodoroTimer(
private var studyTime: Int,
private var breakTime: Int, repeats: Int
): FunctionalTimer(studyTime) {
private var breaksRemaining = repeats
private var isInBreak = false
override fun tick() {
if (time.getTime() == 0 && breaksRemaining == 0){
view = "Done!"
return
}
if (time.getTime() == 0) {
if (isInBreak) {
breaksRemaining--
view = "Focus! ($breaksRemaining breaks remaining)"
time.setTime(studyTime)
} else {
view = "Take a break!"
time.setTime(breakTime)
}
isInBreak = !isInBreak
}
time.minOne()
}
override fun hasEnded(): Boolean {
return breaksRemaining == 0 && time.getTime() == 0
}
}

View file

@ -0,0 +1,19 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
abstract class FunctionalTimer(initialValue: Int) {
protected val time: Time = Time(initialValue)
protected var view: String = "Focus"
fun getHoursMinutesSeconds(): HoursMinutesSeconds {
return time.getAsHMS()
}
fun getViewString(): String {
return view
}
abstract fun tick()
abstract fun hasEnded(): Boolean
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
data class HoursMinutesSeconds(val hours: String, val minutes: String, val seconds: String
)

View file

@ -0,0 +1,35 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
class Time(initialTime: Int) {
private var time = initialTime
fun minOne() {
time--
}
fun plusOne() {
time++
}
fun setTime(newTime: Int) {
time = newTime
}
fun getTime(): Int {
return time
}
fun getAsHMS(): HoursMinutesSeconds {
val hours: Int = time / (60 * 60)
val minutes: Int = (time / (60)) % 60
val seconds: Int = time % 60
return HoursMinutesSeconds(
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0')
)
}
}

View file

@ -0,0 +1,31 @@
package be.ugent.sel.studeez.data.local.models.timer_info
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
class BreakTimerInfo(
name: String,
description: String,
private val studyTime: Int,
private val breakTime: Int,
private val repeats: Int,
id: String = ""
): TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalPomodoroTimer(studyTime, breakTime, repeats)
}
override fun asJson() : Map<String, Any> {
return mapOf(
"type" to "break",
"name" to name,
"description" to description,
"studyTime" to studyTime,
"breakTime" to breakTime,
"repeats" to repeats,
)
}
}

View file

@ -0,0 +1,27 @@
package be.ugent.sel.studeez.data.local.models.timer_info
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
class CustomTimerInfo(
name: String,
description: String,
private val studyTime: Int,
id: String = ""
): TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalCustomTimer(studyTime)
}
override fun asJson() : Map<String, Any> {
return mapOf(
"type" to "custom",
"name" to name,
"description" to description,
"studyTime" to studyTime,
)
}
}

View file

@ -0,0 +1,25 @@
package be.ugent.sel.studeez.data.local.models.timer_info
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
class EndlessTimerInfo(
name: String,
description: String,
id: String = ""
): TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalEndlessTimer()
}
override fun asJson() : Map<String, Any> {
return mapOf(
"type" to "endless",
"name" to name,
"description" to description
)
}
}

View file

@ -0,0 +1,26 @@
package be.ugent.sel.studeez.data.local.models.timer_info
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
/**
* Deze klasse stelt de de info van een timer weer. Elke timer heeft een id, naam en descriptie
*/
abstract class TimerInfo(
val id: String,
val name: String,
val description: String
) {
/**
* Geef de functionele timer terug die kan gebruikt worden tijden een sessie.
*/
abstract fun getFunctionalTimer(): FunctionalTimer
/**
* Geef deze timer weer als json. Wordt gebruikt om terug op te slaan in de databank.
* TODO implementaties hebben nog hardgecodeerde strings.
*/
abstract fun asJson(): Map<String, Any>
}

View file

@ -0,0 +1,16 @@
package be.ugent.sel.studeez.data.local.models.timer_info
import com.google.firebase.firestore.DocumentId
/**
* Timers uit de databank (remote config en firestore) worden als eerste stap omgezet naar dit type.
*/
data class TimerJson(
val type: String = "",
val name: String = "",
val description: String = "",
val studyTime: Int = 0,
val breakTime: Int = 0,
val repeats: Int = 0,
@DocumentId val id: String = ""
)

View file

@ -0,0 +1,7 @@
package be.ugent.sel.studeez.data.local.models.timer_info
enum class TimerType {
BREAK,
ENDLESS,
CUSTOM
}

View file

@ -1,11 +1,7 @@
package be.ugent.sel.studeez.di
import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.UserDAO
import be.ugent.sel.studeez.domain.implementation.FirebaseAccountDAO
import be.ugent.sel.studeez.domain.implementation.FirebaseUserDAO
import be.ugent.sel.studeez.domain.implementation.LogServiceImpl
import be.ugent.sel.studeez.domain.*
import be.ugent.sel.studeez.domain.implementation.*
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -18,6 +14,9 @@ abstract class DatabaseModule {
@Binds abstract fun provideUserDAO(impl: FirebaseUserDAO): UserDAO
@Binds abstract fun provideTimerDAO(impl: FirebaseTimerDAO): TimerDAO
@Binds abstract fun provideLogService(impl: LogServiceImpl): LogService
@Binds abstract fun provideConfigurationService(impl: FirebaseConfigurationService): ConfigurationService
}

View file

@ -0,0 +1,11 @@
package be.ugent.sel.studeez.domain
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
interface ConfigurationService {
suspend fun fetchConfiguration(): Boolean
fun getDefaultTimers(): List<TimerInfo>
}

View file

@ -1,27 +0,0 @@
/*
Copyright 2022 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package be.ugent.sel.studeez.domain
import com.google.firebase.perf.ktx.trace
import com.google.firebase.perf.metrics.Trace
/**
* Trace a block with Firebase performance.
*
* Supports both suspend and regular methods.
*/
inline fun <T> trace(name: String, block: Trace.() -> T): T = Trace.create(name).trace(block)

View file

@ -0,0 +1,19 @@
package be.ugent.sel.studeez.domain
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerJson
import kotlinx.coroutines.flow.Flow
interface TimerDAO {
fun getUserTimers(): Flow<List<TimerInfo>>
fun getAllTimers(): Flow<List<TimerInfo>>
fun saveTimer(newTimer: TimerInfo)
fun updateTimer(timerInfo: TimerInfo)
fun deleteTimer(timerInfo: TimerInfo)
}

View file

@ -0,0 +1,38 @@
package be.ugent.sel.studeez.domain.implementation
import be.ugent.sel.studeez.data.local.models.timer_info.*
import be.ugent.sel.studeez.domain.ConfigurationService
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.get
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.google.gson.Gson
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
class FirebaseConfigurationService @Inject constructor() : ConfigurationService {
init {
// fetch configs elke keer als app wordt opgestart
val configSettings = remoteConfigSettings { minimumFetchIntervalInSeconds = 0 }
remoteConfig.setConfigSettingsAsync(configSettings)
}
private val remoteConfig
get() = Firebase.remoteConfig
override suspend fun fetchConfiguration(): Boolean {
return remoteConfig.fetchAndActivate().await()
}
override fun getDefaultTimers(): List<TimerInfo> {
val jsonString: String = remoteConfig[DEFAULT_TIMERS].asString()
// Json is een lijst van timers
val timerJsonList: List<TimerJson> = ToTimerConverter().jsonToTimerJsonList(jsonString)
return ToTimerConverter().convertToTimerInfoList(timerJsonList)
}
companion object {
private const val DEFAULT_TIMERS = "default_timers"
}
}

View file

@ -0,0 +1,63 @@
package be.ugent.sel.studeez.domain.implementation
import be.ugent.sel.studeez.data.local.models.timer_info.*
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.*
import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.TimerDAO
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ktx.snapshots
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import javax.inject.Inject
class FirebaseTimerDAO @Inject constructor(
private val firestore: FirebaseFirestore,
private val configurationService: FirebaseConfigurationService,
private val auth: AccountDAO
) : TimerDAO {
override fun getUserTimers(): Flow<List<TimerInfo>> {
return currentUserTimersCollection()
.snapshots()
.map { it.toObjects(TimerJson::class.java) }
.map { ToTimerConverter().convertToTimerInfoList(it) }
}
override fun getAllTimers(): Flow<List<TimerInfo>> {
// Wrap default timers in een flow en combineer met de userTimer flow.
val defaultTimers: List<TimerInfo> = configurationService.getDefaultTimers()
val defaultTimersFlow: Flow<List<TimerInfo>> = flowOf(defaultTimers)
val userTimersFlow: Flow<List<TimerInfo>> = getUserTimers()
return defaultTimersFlow.combine(userTimersFlow) { defaultTimersList, userTimersList ->
defaultTimersList + userTimersList
}
}
override fun saveTimer(newTimer: TimerInfo) {
currentUserTimersCollection().add(newTimer.asJson())
}
override fun updateTimer(timerInfo: TimerInfo) {
currentUserTimersCollection().document(timerInfo.id).set(timerInfo.asJson())
}
override fun deleteTimer(timerInfo: TimerInfo) {
currentUserTimersCollection().document(timerInfo.id).delete()
}
private fun currentUserTimersCollection(): CollectionReference =
firestore.collection(USER_COLLECTION)
.document(auth.currentUserId)
.collection(TIMER_COLLECTION)
companion object {
private const val TIMER_COLLECTION = "timers"
private const val USER_COLLECTION = "users"
}
}

View file

@ -0,0 +1,56 @@
package be.ugent.sel.studeez.domain.implementation
import be.ugent.sel.studeez.data.local.models.timer_info.*
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
/**
* Used by ConfigurationService and TimerDAO.
*
* ConfigurationService: configuration is fetched as a JSON-string,
* which is converted into a TimerJson, and converted here into the correct TimerInfo
*
* timerDAO: Timers are being fetched directly to TinerJson and convertes into the correct timerInfo
*/
class ToTimerConverter {
fun interface TimerFactory {
fun makeTimer(map: TimerJson) : TimerInfo
}
private val timerInfoMap: Map<TimerType, TimerFactory> = mapOf(
TimerType.ENDLESS to TimerFactory { EndlessTimerInfo(
it.name,
it.description,
it.id
) },
TimerType.CUSTOM to TimerFactory { CustomTimerInfo(
it.name,
it.description,
it.studyTime,
it.id
) },
TimerType.BREAK to TimerFactory { BreakTimerInfo(
it.name,
it.description,
it.studyTime,
it.breakTime,
it.repeats,
it.id
) }
)
private fun getTimer(timerJson: TimerJson): TimerInfo{
val type: TimerType = TimerType.valueOf(timerJson.type.uppercase())
return timerInfoMap.getValue(type).makeTimer(timerJson)
}
fun convertToTimerInfoList(timerJsonList: List<TimerJson>): List<TimerInfo> {
return timerJsonList.map(this::getTimer)
}
fun jsonToTimerJsonList(json: String): List<TimerJson> {
val type = object : TypeToken<List<TimerJson>>() {}.type
return Gson().fromJson(json, type)
}
}

View file

@ -5,9 +5,11 @@ object StudeezDestinations {
const val SIGN_UP_SCREEN = "signup"
const val LOGIN_SCREEN = "login"
const val HOME_SCREEN = "home"
// const val TASKS_SCREEN = "tasks"
// const val SESSIONS_SCREEN = "sessions"
const val HOME_SCREEN = "home"
const val TIMER_OVERVIEW_SCREEN = "timer_overview"
const val SESSION_SCREEN = "session"
// const val TASKS_SCREEN = "tasks"
// const val SESSIONS_SCREEN = "sessions"
const val PROFILE_SCREEN = "profile"
// const val TIMERS_SCREEN = "timers"

View file

@ -2,6 +2,7 @@ package be.ugent.sel.studeez.screens.drawer
import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.LOGIN_SCREEN
import be.ugent.sel.studeez.screens.StudeezViewModel
@ -22,6 +23,11 @@ class DrawerViewModel @Inject constructor(
// TODO
}
fun onTimersClick(openAndPopup: (String, String) -> Unit) {
// TODO is niet altijd het homescreen
openAndPopup(StudeezDestinations.TIMER_OVERVIEW_SCREEN, StudeezDestinations.HOME_SCREEN)
}
fun onSettingsClick(open: (String) -> Unit) {
// TODO
}

View file

@ -2,6 +2,9 @@ package be.ugent.sel.studeez.screens.home
import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.LOGIN_SCREEN
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@ -12,7 +15,14 @@ class HomeViewModel @Inject constructor(
logService: LogService
) : StudeezViewModel(logService) {
fun onStartSessionClick(open: (String) -> Unit) {
// TODO open(StudeezDestinations.xxx, StudeezDestinations.HOME_SCREEN)
fun onStartSessionClick(openAndPopUp: (String, String) -> Unit) {
openAndPopUp(StudeezDestinations.SESSION_SCREEN, StudeezDestinations.HOME_SCREEN)
}
fun onLogoutClick(openAndPopup: (String, String) -> Unit) {
launchCatching {
accountDAO.signOut()
openAndPopup(LOGIN_SCREEN, HOME_SCREEN)
}
}
}

View file

@ -0,0 +1,58 @@
package be.ugent.sel.studeez.screens.session
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.resources
import kotlinx.coroutines.delay
@Composable
fun SessionScreen(
openAndPopUp: (String, String) -> Unit,
viewModel: SessionViewModel = hiltViewModel()
) {
PrimaryScreenTemplate(
title = resources().getString(R.string.start_session),
openAndPopUp = openAndPopUp
) {
Timer(viewModel)
}
}
@Composable
private fun Timer(viewModel: SessionViewModel = hiltViewModel()) {
var tikker by remember { mutableStateOf(false) }
LaunchedEffect(tikker) {
delay(1000)
viewModel.getTimer().tick()
tikker = !tikker
}
val hms = viewModel.getTimer().getHoursMinutesSeconds()
Column {
Text(
text = "${hms.hours} : ${hms.minutes} : ${hms.seconds}",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 80.sp
)
Text(
text = viewModel.getTimer().getViewString(),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Light,
fontSize = 30.sp
)
}
}

View file

@ -0,0 +1,20 @@
package be.ugent.sel.studeez.screens.session
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class SessionViewModel @Inject constructor(
logService: LogService
) : StudeezViewModel(logService) {
private val timer: FunctionalTimer = FunctionalPomodoroTimer(15, 5, 3)
fun getTimer() : FunctionalTimer {
return timer
}
}

View file

@ -2,6 +2,7 @@ package be.ugent.sel.studeez.screens.splash
import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.ConfigurationService
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
@ -11,10 +12,15 @@ import javax.inject.Inject
@HiltViewModel
class SplashViewModel @Inject constructor(
private val accountDAO: AccountDAO,
private val configurationService: ConfigurationService,
logService: LogService
) : StudeezViewModel(logService) {
val showError = mutableStateOf(false)
init {
launchCatching { configurationService.fetchConfiguration() }
}
fun onAppStart(openAndPopUp: (String, String) -> Unit) {
showError.value = false

View file

@ -0,0 +1,102 @@
package be.ugent.sel.studeez.screens.timer_overview
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.card
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.resources
@Composable
fun TimerOverviewScreen(
openAndPopUp: (String, String) -> Unit,
viewModel: TimerOverviewViewModel = hiltViewModel()
) {
val timers = viewModel.getUserTimers().collectAsState(initial = emptyList())
PrimaryScreenTemplate(
title = resources().getString(R.string.timers),
openAndPopUp = openAndPopUp
) {
Column {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(7.dp)
) {
// Default Timers, cannot be edited
items(viewModel.getDefaultTimers()) {
TimerEntry(timerInfo = it, canEdit = false)
}
// User timers, can be edited
items(timers.value) {
TimerEntry(timerInfo = it, true) { timerInfo ->
viewModel.update(timerInfo)
}
}
}
BasicButton(R.string.add_timer, Modifier.basicButton()) {
// TODO
}
}
}
}
@Composable
fun TimerEntry(timerInfo: TimerInfo, canEdit: Boolean, update: (TimerInfo) -> Unit = {}) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(
text = timerInfo.name,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
Text(
text = timerInfo.description,
fontWeight = FontWeight.Light,
fontSize = 15.sp
)
}
if (canEdit) {
BasicButton(R.string.edit, Modifier.card()) {
// TODO
}
}
}
}
@Preview
@Composable
fun TimerEntryPreview() {
val timerInfo = CustomTimerInfo(
"my preview timer",
"This is the description of the timer",
60
)
TimerEntry(timerInfo = timerInfo, true) { }
}

View file

@ -0,0 +1,34 @@
package be.ugent.sel.studeez.screens.timer_overview
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.domain.ConfigurationService
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.TimerDAO
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@HiltViewModel
class TimerOverviewViewModel @Inject constructor(
private val configurationService: ConfigurationService,
private val timerDAO: TimerDAO,
logService: LogService
) : StudeezViewModel(logService) {
fun getUserTimers() : Flow<List<TimerInfo>> {
return timerDAO.getUserTimers()
}
fun getDefaultTimers(): List<TimerInfo> {
return configurationService.getDefaultTimers()
}
fun update(timerInfo: TimerInfo) = timerDAO.updateTimer(timerInfo)
fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo)
fun save(timerInfo: TimerInfo) = timerDAO.saveTimer(timerInfo)
}

View file

@ -61,6 +61,8 @@
<!-- Timers -->
<string name="timers">Timers</string>
<string name="edit">Edit</string>
<string name="add_timer">Add timer</string>
<!-- Settings -->
<string name="settings">Settings</string>