commit
						f963256ec2
					
				
					 29 changed files with 699 additions and 38 deletions
				
			
		|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|     } | ||||
| } | ||||
|  | @ -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() | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
| 
 | ||||
| } | ||||
|  | @ -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 | ||||
| ) | ||||
|  | @ -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') | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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 | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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> | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | @ -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 = "" | ||||
| ) | ||||
|  | @ -0,0 +1,7 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_info | ||||
| 
 | ||||
| enum class TimerType { | ||||
|     BREAK, | ||||
|     ENDLESS, | ||||
|     CUSTOM | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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> | ||||
| 
 | ||||
| } | ||||
|  | @ -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) | ||||
							
								
								
									
										19
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/TimerDAO.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/TimerDAO.kt
									
										
									
									
									
										Normal 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) | ||||
| 
 | ||||
| } | ||||
|  | @ -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" | ||||
|     } | ||||
| } | ||||
|  | @ -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" | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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) | ||||
|     } | ||||
| } | ||||
|  | @ -6,8 +6,10 @@ object StudeezDestinations { | |||
|     const val LOGIN_SCREEN = "login" | ||||
| 
 | ||||
|      const val HOME_SCREEN = "home" | ||||
| //    const val TASKS_SCREEN = "tasks" | ||||
| //    const val SESSIONS_SCREEN = "sessions" | ||||
|     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" | ||||
|  |  | |||
|  | @ -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 | ||||
|     } | ||||
|  |  | |||
|  | @ -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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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) { } | ||||
| } | ||||
|  | @ -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) | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -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> | ||||
|  |  | |||
		Reference in a new issue
	
	 lbarraga
						lbarraga