fix mergeconflict when merging tasksoverview

This commit is contained in:
brreynie 2023-05-04 22:12:08 +02:00
commit e2ada0b9d4
67 changed files with 1833 additions and 281 deletions

View file

@ -66,6 +66,7 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.compose.material:material:1.2.0' implementation 'androidx.compose.material:material:1.2.0'
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
// ViewModel // ViewModel
@ -97,6 +98,9 @@ dependencies {
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
// Coroutine testing
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
// Mocking // Mocking
testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0' testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0'

View file

@ -38,6 +38,8 @@ import be.ugent.sel.studeez.screens.profile.EditProfileRoute
import be.ugent.sel.studeez.screens.profile.ProfileRoute import be.ugent.sel.studeez.screens.profile.ProfileRoute
import be.ugent.sel.studeez.screens.session.SessionRoute import be.ugent.sel.studeez.screens.session.SessionRoute
import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute
import be.ugent.sel.studeez.screens.sessions.SessionsRoute
import be.ugent.sel.studeez.screens.settings.SettingsRoute
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
import be.ugent.sel.studeez.screens.splash.SplashRoute import be.ugent.sel.studeez.screens.splash.SplashRoute
import be.ugent.sel.studeez.screens.tasks.SubjectRoute import be.ugent.sel.studeez.screens.tasks.SubjectRoute
@ -46,7 +48,9 @@ import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute
import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute
import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute
import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute
import be.ugent.sel.studeez.screens.timer_edit.TimerEditRoute
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute
import be.ugent.sel.studeez.screens.timer_overview.add_timer.AddTimerRoute
import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -97,7 +101,7 @@ fun resources(): Resources {
@Composable @Composable
fun StudeezNavGraph( fun StudeezNavGraph(
appState: StudeezAppstate, appState: StudeezAppstate,
modifier: Modifier, modifier: Modifier = Modifier,
) { ) {
val drawerViewModel: DrawerViewModel = hiltViewModel() val drawerViewModel: DrawerViewModel = hiltViewModel()
val navBarViewModel: NavigationBarViewModel = hiltViewModel() val navBarViewModel: NavigationBarViewModel = hiltViewModel()
@ -119,35 +123,13 @@ fun StudeezNavGraph(
startDestination = StudeezDestinations.SPLASH_SCREEN, startDestination = StudeezDestinations.SPLASH_SCREEN,
modifier = modifier, modifier = modifier,
) { ) {
// NavBar
composable(StudeezDestinations.SPLASH_SCREEN) {
SplashRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.LOGIN_SCREEN) {
LoginRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SIGN_UP_SCREEN) {
SignUpRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.HOME_SCREEN) { composable(StudeezDestinations.HOME_SCREEN) {
HomeRoute( HomeRoute(
open, open,
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
drawerActions = drawerActions, drawerActions = drawerActions,
navigationBarActions = navigationBarActions, navigationBarActions = navigationBarActions
) )
} }
@ -176,6 +158,14 @@ fun StudeezNavGraph(
) )
} }
composable(StudeezDestinations.TASKS_SCREEN) {
TaskRoute(
goBack = goBack,
open = open,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.ADD_TASK_FORM) { composable(StudeezDestinations.ADD_TASK_FORM) {
TaskAddRoute( TaskAddRoute(
goBack = goBack, goBack = goBack,
@ -192,16 +182,14 @@ fun StudeezNavGraph(
) )
} }
composable(StudeezDestinations.TASKS_SCREEN) {
TaskRoute( composable(StudeezDestinations.SESSIONS_SCREEN) {
goBack = goBack, SessionsRoute(
open = open, drawerActions = drawerActions,
viewModel = hiltViewModel(), navigationBarActions = navigationBarActions
) )
} }
// TODO Sessions screen
composable(StudeezDestinations.PROFILE_SCREEN) { composable(StudeezDestinations.PROFILE_SCREEN) {
ProfileRoute( ProfileRoute(
open, open,
@ -211,10 +199,49 @@ fun StudeezNavGraph(
) )
} }
composable(StudeezDestinations.TIMER_OVERVIEW_SCREEN) { // Drawer
composable(StudeezDestinations.TIMER_SCREEN) {
TimerOverviewRoute( TimerOverviewRoute(
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
drawerActions = drawerActions, drawerActions = drawerActions,
open = open
)
}
composable(StudeezDestinations.SETTINGS_SCREEN) {
SettingsRoute(
drawerActions = drawerActions
)
}
// Login flow
composable(StudeezDestinations.SPLASH_SCREEN) {
SplashRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.LOGIN_SCREEN) {
LoginRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SIGN_UP_SCREEN) {
SignUpRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
// Studying flow
composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open,
goBack,
viewModel = hiltViewModel(),
) )
} }
@ -226,10 +253,43 @@ fun StudeezNavGraph(
) )
} }
// TODO Timers screen composable(StudeezDestinations.SESSION_RECAP) {
// TODO Settings screen SessionRecapRoute(
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.ADD_TIMER_SCREEN) {
AddTimerRoute(
open = open,
goBack = goBack,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.TIMER_EDIT_SCREEN) {
TimerEditRoute(
open = open,
popUp = goBack,
viewModel = hiltViewModel()
)
}
// Friends flow
composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) {
// TODO
}
// Create & edit screens
composable(StudeezDestinations.CREATE_TASK_SCREEN) {
// TODO
}
composable(StudeezDestinations.CREATE_SESSION_SCREEN) {
// TODO
}
// Edit screens
composable(StudeezDestinations.EDIT_PROFILE_SCREEN) { composable(StudeezDestinations.EDIT_PROFILE_SCREEN) {
EditProfileRoute( EditProfileRoute(
goBack, goBack,
@ -237,20 +297,5 @@ fun StudeezNavGraph(
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
) )
} }
composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open,
goBack,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SESSION_RECAP) {
SessionRecapRoute(
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel()
)
}
} }
} }

View file

@ -10,9 +10,15 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.lifecycleScope
import be.ugent.sel.studeez.StudeezApp import be.ugent.sel.studeez.StudeezApp
import be.ugent.sel.studeez.screens.session.InvisibleSessionManager
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
var onTimerInvisible: Job? = null
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -30,6 +36,18 @@ class MainActivity : ComponentActivity() {
} }
} }
} }
override fun onStop() {
onTimerInvisible = lifecycleScope.launch {
InvisibleSessionManager.updateTimer()
}
super.onStop()
}
override fun onStart() {
onTimerInvisible?.cancel()
super.onStart()
}
} }
@Composable @Composable

View file

@ -27,11 +27,19 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.card import be.ugent.sel.studeez.common.ext.card
import be.ugent.sel.studeez.common.ext.defaultButtonShape
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@Composable @Composable
fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) {
TextButton(onClick = action, modifier = modifier) { Text(text = stringResource(text)) } TextButton(
onClick = action,
modifier = modifier
) {
Text(
text = stringResource(text)
)
}
} }
@Composable @Composable
@ -45,7 +53,7 @@ fun BasicButton(
Button( Button(
onClick = onClick, onClick = onClick,
modifier = modifier, modifier = modifier,
shape = RoundedCornerShape(20.dp), shape = defaultButtonShape(),
colors = colors, colors = colors,
border = border, border = border,
) { ) {

View file

@ -1,7 +1,9 @@
package be.ugent.sel.studeez.common.composable package be.ugent.sel.studeez.common.composable
import androidx.compose.foundation.layout.Column import androidx.compose.animation.core.animateFloat
import androidx.compose.foundation.layout.Row import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.material.FloatingActionButton import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
@ -11,52 +13,134 @@ import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.DateRange import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import be.ugent.sel.studeez.R.string as AppText
const val TRANSITION = "transition"
val HEIGHT_DIFFERENCE = 30.dp
data class AddButtonActions(
val onTaskClick: () -> Unit,
val onFriendClick: () -> Unit,
val onSessionClick: () -> Unit
)
@Composable @Composable
fun CollapsedAddButton() { fun AddButton(
FloatingActionButton( addButtonActions: AddButtonActions
onClick = { /* TODO popup add options */ } ) {
var isExpanded by remember { mutableStateOf(false) }
// Rotate the button when expanded, normal when collapsed.
val transition = updateTransition(targetState = isExpanded, label = TRANSITION)
val rotate by transition.animateFloat(label = TRANSITION) { expanded -> if (expanded) 315f else 0f }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) { ) {
Icon(imageVector = Icons.Default.Add, contentDescription = "fab") Box {
// Show minis when expanded.
if (isExpanded) {
ExpandedAddButton(
addButtonActions = addButtonActions
)
}
}
// The base add button
FloatingActionButton(
onClick = {
// Toggle expanded/collapsed.
isExpanded = !isExpanded
},
modifier = Modifier.padding(bottom = if (isExpanded) 78.dp else 0.dp)
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "fab",
modifier = Modifier.rotate(rotate) // The rotation
)
}
} }
} }
@Composable @Composable
fun ExpandedAddButton() { fun ExpandedAddButton(
Row() { addButtonActions: AddButtonActions
IconButton(onClick = { /* TODO Go to next step */ }) { ) {
Column (horizontalAlignment = Alignment.CenterHorizontally) { Row {
Icon(imageVector = Icons.Default.Check, contentDescription = "Task") ExpandedEntry(
Text(text = "Task") name = AppText.task,
} imageVector = Icons.Default.Check,
} onClick = addButtonActions.onTaskClick,
IconButton(onClick = { /* TODO Go to next step */ }) { modifier = Modifier.padding(36.dp, HEIGHT_DIFFERENCE, 36.dp, 0.dp)
Column (horizontalAlignment = Alignment.CenterHorizontally) { )
Icon(imageVector = Icons.Default.Person, contentDescription = "Friend")
Text(text = "Friend") ExpandedEntry(
} name = AppText.friend,
} imageVector = Icons.Default.Person,
IconButton(onClick = { /* TODO Go to next step */ }) { onClick = addButtonActions.onFriendClick
Column (horizontalAlignment = Alignment.CenterHorizontally) { )
Icon(imageVector = Icons.Default.DateRange, contentDescription = "Session")
Text(text = "Session") ExpandedEntry(
} name = AppText.session,
} imageVector = Icons.Default.DateRange,
onClick = addButtonActions.onSessionClick,
modifier = Modifier.padding(36.dp, HEIGHT_DIFFERENCE, 36.dp, 0.dp)
)
} }
} }
@Composable
fun ExpandedEntry(
name: Int,
imageVector: ImageVector,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
IconButton(
onClick = onClick,
modifier = modifier
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
imageVector = imageVector,
contentDescription = resources().getString(name),
// TODO Dark overlay
// tint = colors.surface
)
Text(
text = resources().getString(name),
// TODO Dark overlay
// color = colors.surface
)
}
}
}
@Preview @Preview
@Composable @Composable
fun CollapsedAddButtonPreview() { fun AddButtonPreview() {
StudeezTheme { CollapsedAddButton() } StudeezTheme { AddButton(
addButtonActions = AddButtonActions({}, {}, {})
)}
} }
@Preview @Preview
@Composable @Composable
fun ExpandedAddButtonPreview() { fun ExpandedAddButtonPreview() {
StudeezTheme { ExpandedAddButton() } StudeezTheme { ExpandedAddButton (
addButtonActions = AddButtonActions({}, {}, {})
) }
} }

View file

@ -57,7 +57,11 @@ fun PrimaryScreenTemplate(
bottomBar = { NavigationBar(navigationBarActions) }, bottomBar = { NavigationBar(navigationBarActions) },
floatingActionButtonPosition = FabPosition.Center, floatingActionButtonPosition = FabPosition.Center,
isFloatingActionButtonDocked = true, isFloatingActionButtonDocked = true,
floatingActionButton = { CollapsedAddButton() } floatingActionButton = { AddButton(AddButtonActions(
onTaskClick = navigationBarActions.onAddTaskClick,
onFriendClick = navigationBarActions.onAddFriendClick,
onSessionClick = navigationBarActions.onAddSessionClick
)) }
) { ) {
content(it) content(it)
} }
@ -70,7 +74,7 @@ fun PrimaryScreenPreview() {
PrimaryScreenTemplate( PrimaryScreenTemplate(
"Preview screen", "Preview screen",
DrawerActions({}, {}, {}, {}, {}), DrawerActions({}, {}, {}, {}, {}),
NavigationBarActions({ false }, {}, {}, {}, {}), NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
{ {
IconButton(onClick = { /*TODO*/ }) { IconButton(onClick = { /*TODO*/ }) {
Icon( Icon(

View file

@ -41,10 +41,12 @@ fun BasicField(
fun LabelledInputField( fun LabelledInputField(
value: String, value: String,
onNewValue: (String) -> Unit, onNewValue: (String) -> Unit,
@StringRes label: Int @StringRes label: Int,
singleLine: Boolean = false
) { ) {
OutlinedTextField( OutlinedTextField(
value = value, value = value,
singleLine = singleLine,
onValueChange = onNewValue, onValueChange = onNewValue,
label = { Text(text = stringResource(id = label)) }, label = { Text(text = stringResource(id = label)) },
modifier = Modifier.fieldModifier() modifier = Modifier.fieldModifier()

View file

@ -0,0 +1,115 @@
package be.ugent.sel.studeez.common.composable
import android.app.TimePickerDialog
import android.app.TimePickerDialog.OnTimeSetListener
import android.content.Context
import androidx.annotation.StringRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import be.ugent.sel.studeez.ui.theme.StudeezTheme
@Composable
fun TimePickerCard(
@StringRes text: Int,
initialSeconds: Int,
onTimeChosen: (Int) -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.fieldModifier(),
elevation = 10.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.fieldModifier(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = text),
fontWeight = FontWeight.Medium
)
TimePickerButton(
initialSeconds = initialSeconds,
onTimeChosen = onTimeChosen
)
}
}
}
@Composable
fun TimePickerButton(
initialSeconds: Int,
modifier: Modifier = Modifier,
colors: ButtonColors = ButtonDefaults.buttonColors(),
border: BorderStroke? = null,
onTimeChosen: (Int) -> Unit
) {
val context = LocalContext.current
val timeState: MutableState<Int> = remember {
mutableStateOf(initialSeconds)
}
Button(
onClick = { pickDuration(context, onTimeChosen, timeState) },
modifier = modifier,
shape = RoundedCornerShape(20.dp),
colors = colors,
border = border
) {
Text(text = HoursMinutesSeconds(timeState.value).toString())
}
}
private fun pickDuration(context: Context, onTimeChosen: (Int) -> Unit, timeState: MutableState<Int>) {
val listener = OnTimeSetListener { _, hour, minute ->
timeState.value = HoursMinutesSeconds(hour, minute, 0).getTotalSeconds()
onTimeChosen(timeState.value)
}
val hms = HoursMinutesSeconds(timeState.value)
val mTimePickerDialog = TimePickerDialog(
context,
listener,
hms.hours,
hms.minutes,
true
)
mTimePickerDialog.show()
}
@Preview
@Composable
fun TimePickerButtonPreview() {
StudeezTheme {
TimePickerButton(initialSeconds = 5 * 60 + 12, onTimeChosen = {})
}
}
@Preview
@Composable
fun TimePickerCardPreview() {
StudeezTheme {
TimePickerCard(text = R.string.studyTime, initialSeconds = 5 * 60 + 12, onTimeChosen = {})
}
}

View file

@ -1,5 +1,6 @@
package be.ugent.sel.studeez.common.composable.drawer package be.ugent.sel.studeez.common.composable.drawer
import android.content.Context
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -16,6 +17,7 @@ import androidx.compose.material.icons.outlined.Info
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
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
@ -28,7 +30,7 @@ data class DrawerActions(
val onTimersClick: () -> Unit, val onTimersClick: () -> Unit,
val onSettingsClick: () -> Unit, val onSettingsClick: () -> Unit,
val onLogoutClick: () -> Unit, val onLogoutClick: () -> Unit,
val onAboutClick: () -> Unit, val onAboutClick: (Context) -> Unit,
) )
fun getDrawerActions( fun getDrawerActions(
@ -41,7 +43,7 @@ fun getDrawerActions(
onTimersClick = { drawerViewModel.onTimersClick(open) }, onTimersClick = { drawerViewModel.onTimersClick(open) },
onSettingsClick = { drawerViewModel.onSettingsClick(open) }, onSettingsClick = { drawerViewModel.onSettingsClick(open) },
onLogoutClick = { drawerViewModel.onLogoutClick(openAndPopUp) }, onLogoutClick = { drawerViewModel.onLogoutClick(openAndPopUp) },
onAboutClick = { drawerViewModel.onAboutClick(open) }, onAboutClick = { context -> drawerViewModel.onAboutClick(open, context = context) },
) )
} }
@ -79,10 +81,11 @@ fun Drawer(
) )
} }
val context = LocalContext.current
DrawerEntry( DrawerEntry(
icon = Icons.Outlined.Info, icon = Icons.Outlined.Info,
text = resources().getString(R.string.about), text = resources().getString(R.string.about),
onClick = drawerActions.onAboutClick, onClick = { drawerActions.onAboutClick(context) },
) )
} }
} }
@ -96,7 +99,7 @@ fun DrawerEntry(
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.clickable(onClick = { onClick() }) .clickable(onClick = onClick)
.fillMaxWidth() .fillMaxWidth()
) { ) {
Box( Box(

View file

@ -1,5 +1,10 @@
package be.ugent.sel.studeez.common.composable.drawer package be.ugent.sel.studeez.common.composable.drawer
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import be.ugent.sel.studeez.domain.AccountDAO import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.LogService import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.navigation.StudeezDestinations import be.ugent.sel.studeez.navigation.StudeezDestinations
@ -9,6 +14,8 @@ import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
const val REPO_URL: String = "https://github.ugent.be/SELab1/project2023-groep14/"
@HiltViewModel @HiltViewModel
class DrawerViewModel @Inject constructor( class DrawerViewModel @Inject constructor(
private val accountDAO: AccountDAO, private val accountDAO: AccountDAO,
@ -20,11 +27,11 @@ class DrawerViewModel @Inject constructor(
} }
fun onTimersClick(openAndPopup: (String) -> Unit) { fun onTimersClick(openAndPopup: (String) -> Unit) {
openAndPopup(StudeezDestinations.TIMER_OVERVIEW_SCREEN) openAndPopup(StudeezDestinations.TIMER_SCREEN)
} }
fun onSettingsClick(open: (String) -> Unit) { fun onSettingsClick(open: (String) -> Unit) {
// TODO open(StudeezDestinations.SETTINGS_SCREEN)
} }
fun onLogoutClick(openAndPopUp: (String, String) -> Unit) { fun onLogoutClick(openAndPopUp: (String, String) -> Unit) {
@ -34,7 +41,8 @@ class DrawerViewModel @Inject constructor(
} }
} }
fun onAboutClick(open: (String) -> Unit) { fun onAboutClick(open: (String) -> Unit, context: Context) {
// TODO val intent = Intent(Intent.ACTION_VIEW, Uri.parse(REPO_URL))
context.startActivity(intent)
} }
} }

View file

@ -1,6 +1,5 @@
package be.ugent.sel.studeez.common.composable.navbar package be.ugent.sel.studeez.common.composable.navbar
import android.util.Log
import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon import androidx.compose.material.Icon
@ -15,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
@ -22,10 +22,16 @@ import be.ugent.sel.studeez.R.string as AppText
data class NavigationBarActions( data class NavigationBarActions(
val isSelectedTab: (String) -> Boolean, val isSelectedTab: (String) -> Boolean,
val onHomeClick: () -> Unit, val onHomeClick: () -> Unit,
val onTasksClick: () -> Unit, val onTasksClick: () -> Unit,
val onSessionsClick: () -> Unit, val onSessionsClick: () -> Unit,
val onProfileClick: () -> Unit, val onProfileClick: () -> Unit,
// AddButton
val onAddTaskClick: () -> Unit,
val onAddFriendClick: () -> Unit,
val onAddSessionClick: () -> Unit
) )
fun getNavigationBarActions( fun getNavigationBarActions(
@ -49,6 +55,16 @@ fun getNavigationBarActions(
onProfileClick = { onProfileClick = {
navigationBarViewModel.onProfileClick(open) navigationBarViewModel.onProfileClick(open)
}, },
onAddTaskClick = {
navigationBarViewModel.onAddTaskClick(open)
},
onAddFriendClick = {
navigationBarViewModel.onAddFriendClick(open)
},
onAddSessionClick = {
navigationBarViewModel.onAddSessionClick(open)
}
) )
} }
@ -78,7 +94,7 @@ fun NavigationBar(
) )
// Hack to space the entries in the navigation bar, make space for fab // Hack to space the entries in the navigation bar, make space for fab
BottomNavigationItem(icon = {}, onClick = {}, selected = false) BottomNavigationItem(icon = {}, onClick = {}, selected = false, enabled = false)
BottomNavigationItem( BottomNavigationItem(
icon = { icon = {
@ -87,8 +103,7 @@ fun NavigationBar(
) )
}, },
label = { Text(text = resources().getString(AppText.sessions)) }, label = { Text(text = resources().getString(AppText.sessions)) },
// TODO selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN), selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN),
selected = false,
onClick = navigationBarActions.onSessionsClick onClick = navigationBarActions.onSessionsClick
) )
@ -111,7 +126,7 @@ fun NavigationBar(
fun NavigationBarPreview() { fun NavigationBarPreview() {
StudeezTheme { StudeezTheme {
NavigationBar( NavigationBar(
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
) )
} }
} }

View file

@ -1,13 +1,15 @@
package be.ugent.sel.studeez.common.composable.navbar package be.ugent.sel.studeez.common.composable.navbar
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.domain.LogService import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.TASKS_SCREEN
import be.ugent.sel.studeez.screens.StudeezViewModel import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import be.ugent.sel.studeez.R.string as AppText
@HiltViewModel @HiltViewModel
class NavigationBarViewModel @Inject constructor( class NavigationBarViewModel @Inject constructor(
@ -23,10 +25,25 @@ class NavigationBarViewModel @Inject constructor(
} }
fun onSessionsClick(open: (String) -> Unit) { fun onSessionsClick(open: (String) -> Unit) {
// TODO open(SESSIONS_SCREEN)
} }
fun onProfileClick(open: (String) -> Unit) { fun onProfileClick(open: (String) -> Unit) {
open(PROFILE_SCREEN) open(PROFILE_SCREEN)
} }
fun onAddTaskClick(open: (String) -> Unit) {
// TODO open(CREATE_TASK_SCREEN)
SnackbarManager.showMessage(AppText.create_task_not_possible_yet) // TODO Remove
}
fun onAddFriendClick(open: (String) -> Unit) {
// TODO open(SEARCH_FRIENDS_SCREEN)
SnackbarManager.showMessage(AppText.add_friend_not_possible_yet) // TODO Remove
}
fun onAddSessionClick(open: (String) -> Unit) {
// TODO open(CREATE_SESSION_SCREEN)
SnackbarManager.showMessage(AppText.create_session_not_possible_yet) // TODO Remove
}
} }

View file

@ -0,0 +1,24 @@
package be.ugent.sel.studeez.common.composable.navbar
import android.app.TimePickerDialog
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
@Composable
fun BasicTimePicker(
onHoursChange: (Int) -> Unit,
onMinutesChange: (Int) -> Unit,
Hours: Int,
Minutes: Int,
): TimePickerDialog {
return TimePickerDialog(
LocalContext.current,
{ _, mHour: Int, mMinute: Int ->
onHoursChange(mHour)
onMinutesChange(mMinute)
},
Hours,
Minutes,
true
)
}

View file

@ -0,0 +1,8 @@
package be.ugent.sel.studeez.common.ext
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.unit.dp
fun defaultButtonShape(): RoundedCornerShape {
return RoundedCornerShape(20.dp)
}

View file

@ -0,0 +1,11 @@
package be.ugent.sel.studeez.data
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class EditTimerState @Inject constructor(){
lateinit var timerInfo: TimerInfo
}

View file

@ -1,14 +1,10 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.data.local.models.SessionReport
import be.ugent.sel.studeez.screens.session.sessionScreens.CustomSessionScreen
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) { class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) {
override fun tick() { override fun tick() {
if (!hasEnded()) { if (!hasEnded()) {
time.minOne() time--
totalStudyTime++ totalStudyTime++
} }
} }

View file

@ -11,7 +11,7 @@ class FunctionalEndlessTimer : FunctionalTimer(0) {
} }
override fun tick() { override fun tick() {
time.plusOne() time++
totalStudyTime++ totalStudyTime++
} }

View file

@ -1,8 +1,5 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.screens.session.sessionScreens.BreakSessionScreen
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
class FunctionalPomodoroTimer( class FunctionalPomodoroTimer(
private var studyTime: Int, private var studyTime: Int,
private var breakTime: Int, repeats: Int private var breakTime: Int, repeats: Int
@ -25,7 +22,7 @@ class FunctionalPomodoroTimer(
} }
isInBreak = !isInBreak isInBreak = !isInBreak
} }
time.minOne() time--
if (!isInBreak) { if (!isInBreak) {
totalStudyTime++ totalStudyTime++

View file

@ -1,11 +1,10 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.data.local.models.SessionReport import be.ugent.sel.studeez.data.local.models.SessionReport
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
import com.google.firebase.Timestamp import com.google.firebase.Timestamp
abstract class FunctionalTimer(initialValue: Int) { abstract class FunctionalTimer(initialValue: Int) {
val time: Time = Time(initialValue) var time: Time = Time(initialValue)
var totalStudyTime: Int = 0 var totalStudyTime: Int = 0
fun getHoursMinutesSeconds(): HoursMinutesSeconds { fun getHoursMinutesSeconds(): HoursMinutesSeconds {

View file

@ -1,17 +1,21 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) {
constructor(seconds: Int) : this(
hours = seconds / (60 * 60), constructor(sec: Int): this(
minutes = (seconds / 60) % 60, hours = sec / (60 * 60),
seconds = seconds % 60, minutes = (sec / (60)) % 60,
seconds = sec % 60,
) )
fun getTotalSeconds(): Int {
return (hours * 60 * 60) + (minutes * 60) + seconds
}
override fun toString(): String { override fun toString(): String {
return hours.toString().padStart(2, '0') + val hoursString = hours.toString().padStart(2, '0')
":" + val minutesString = minutes.toString().padStart(2, '0')
minutes.toString().padStart(2, '0') + val secondsString = seconds.toString().padStart(2, '0')
":" + return "$hoursString:$minutesString:$secondsString"
seconds.toString().padStart(2, '0')
} }
} }

View file

@ -1,16 +1,11 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
class Time(initialTime: Int) { class Time(var time: Int) {
operator fun invoke() = time
var time = initialTime operator fun inc(): Time = Time(time + 1)
fun minOne() { operator fun dec(): Time = Time(time - 1)
time--
}
fun plusOne() {
time++
}
fun getAsHMS(): HoursMinutesSeconds { fun getAsHMS(): HoursMinutesSeconds {
return HoursMinutesSeconds(time) return HoursMinutesSeconds(time)

View file

@ -6,11 +6,10 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
class CustomTimerInfo( class CustomTimerInfo(
name: String, name: String,
description: String, description: String,
private val studyTime: Int, var studyTime: Int,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ): TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer { override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalCustomTimer(studyTime) return FunctionalCustomTimer(studyTime)
} }
@ -24,4 +23,8 @@ class CustomTimerInfo(
) )
} }
override fun <T> accept(visitor: TimerInfoVisitor<T>): T {
return visitor.visitCustomTimerInfo(this)
}
} }

View file

@ -22,4 +22,8 @@ class EndlessTimerInfo(
) )
} }
override fun <T> accept(visitor: TimerInfoVisitor<T>): T {
return visitor.visitEndlessTimerInfo(this)
}
} }

View file

@ -2,13 +2,14 @@ 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.FunctionalPomodoroTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor
class PomodoroTimerInfo( class PomodoroTimerInfo(
name: String, name: String,
description: String, description: String,
private val studyTime: Int, var studyTime: Int,
private val breakTime: Int, var breakTime: Int,
private val repeats: Int, val repeats: Int,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ): TimerInfo(id, name, description) {
@ -28,4 +29,8 @@ class PomodoroTimerInfo(
) )
} }
override fun <T> accept(visitor: TimerInfoVisitor<T>): T {
return visitor.visitBreakTimerInfo(this)
}
} }

View file

@ -7,8 +7,8 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
*/ */
abstract class TimerInfo( abstract class TimerInfo(
val id: String, val id: String,
val name: String, var name: String,
val description: String var description: String
) { ) {
/** /**
@ -21,6 +21,7 @@ abstract class TimerInfo(
* TODO implementaties hebben nog hardgecodeerde strings. * TODO implementaties hebben nog hardgecodeerde strings.
*/ */
abstract fun asJson(): Map<String, Any> abstract fun asJson(): Map<String, Any>
abstract fun <T> accept(visitor: TimerInfoVisitor<T>): T
} }

View file

@ -0,0 +1,11 @@
package be.ugent.sel.studeez.data.local.models.timer_info
interface TimerInfoVisitor<T> {
fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): T
fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): T
fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): T
}

View file

@ -1,29 +1,40 @@
package be.ugent.sel.studeez.navigation package be.ugent.sel.studeez.navigation
object StudeezDestinations { object StudeezDestinations {
const val SPLASH_SCREEN = "splash" // NavBar
const val SIGN_UP_SCREEN = "signup"
const val LOGIN_SCREEN = "login"
const val HOME_SCREEN = "home" const val HOME_SCREEN = "home"
const val TIMER_OVERVIEW_SCREEN = "timer_overview" const val SUBJECT_SCREEN = "subjects"
const val SESSIONS_SCREEN = "sessions"
const val PROFILE_SCREEN = "profile"
// Drawer
const val TIMER_SCREEN = "timer_overview"
const val SETTINGS_SCREEN = "settings"
// Login flow
const val SPLASH_SCREEN = "splash"
const val LOGIN_SCREEN = "login"
const val SIGN_UP_SCREEN = "signup"
// Studying flow
const val TIMER_SELECTION_SCREEN = "timer_selection" const val TIMER_SELECTION_SCREEN = "timer_selection"
const val TIMER_EDIT_SCREEN = "timer_edit"
const val SESSION_SCREEN = "session" const val SESSION_SCREEN = "session"
const val SESSION_RECAP = "session_recap" const val SESSION_RECAP = "session_recap"
const val SUBJECT_SCREEN = "subjects"
const val ADD_SUBJECT_FORM = "add_subject" const val ADD_SUBJECT_FORM = "add_subject"
const val EDIT_SUBJECT_FORM = "edit_subject" const val EDIT_SUBJECT_FORM = "edit_subject"
const val TASKS_SCREEN = "tasks" const val TASKS_SCREEN = "tasks"
const val ADD_TASK_FORM = "add_task" const val ADD_TASK_FORM = "add_task"
const val EDIT_TASK_FORM = "edit_task" const val EDIT_TASK_FORM = "edit_task"
// const val SESSIONS_SCREEN = "sessions" // Friends flow
const val PROFILE_SCREEN = "profile" const val SEARCH_FRIENDS_SCREEN = "search_friends"
// const val TIMERS_SCREEN = "timers" // Create & edit screens
// const val SETTINGS_SCREEN = "settings" const val CREATE_TASK_SCREEN = "create_task"
const val CREATE_SESSION_SCREEN = "create_session"
// Edit screens
const val EDIT_PROFILE_SCREEN = "edit_profile" const val EDIT_PROFILE_SCREEN = "edit_profile"
const val ADD_TIMER_SCREEN = "add_timer"
} }

View file

@ -33,13 +33,13 @@ fun HomeRoute(
fun HomeScreen( fun HomeScreen(
onStartSessionClick: () -> Unit, onStartSessionClick: () -> Unit,
drawerActions: DrawerActions, drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions, navigationBarActions: NavigationBarActions
) { ) {
PrimaryScreenTemplate( PrimaryScreenTemplate(
title = resources().getString(R.string.home), title = resources().getString(R.string.home),
drawerActions = drawerActions, drawerActions = drawerActions,
navigationBarActions = navigationBarActions, navigationBarActions = navigationBarActions,
barAction = { FriendsAction() } // TODO barAction = { FriendsAction() }
) { ) {
BasicButton(R.string.start_session, Modifier.basicButton()) { BasicButton(R.string.start_session, Modifier.basicButton()) {
onStartSessionClick() onStartSessionClick()
@ -63,6 +63,6 @@ fun HomeScreenPreview() {
HomeScreen( HomeScreen(
onStartSessionClick = {}, onStartSessionClick = {},
drawerActions = DrawerActions({}, {}, {}, {}, {}), drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}) navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {})
) )
} }

View file

@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicTextButton import be.ugent.sel.studeez.common.composable.BasicTextButton
import be.ugent.sel.studeez.common.composable.LabelledInputField import be.ugent.sel.studeez.common.composable.LabelledInputField
@ -64,7 +63,10 @@ fun EditProfileScreen(
BasicTextButton( BasicTextButton(
text = R.string.save, text = R.string.save,
Modifier.textButton(), Modifier.textButton(),
action = editProfileActions.onSaveClick action = {
editProfileActions.onSaveClick()
goBack()
}
) )
BasicTextButton( BasicTextButton(
text = R.string.delete_profile, text = R.string.delete_profile,

View file

@ -88,6 +88,6 @@ fun ProfileScreenPreview() {
ProfileScreen( ProfileScreen(
profileActions = ProfileActions({ null }, {}), profileActions = ProfileActions({ null }, {}),
drawerActions = DrawerActions({}, {}, {}, {}, {}), drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}) navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {})
) )
} }

View file

@ -0,0 +1,29 @@
package be.ugent.sel.studeez.screens.session
import android.media.MediaPlayer
import kotlinx.coroutines.delay
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds
@Singleton
object InvisibleSessionManager {
private var viewModel: SessionViewModel? = null
private lateinit var mediaPlayer: MediaPlayer
fun setParameters(viewModel: SessionViewModel, mediaplayer: MediaPlayer) {
this.viewModel = viewModel
this.mediaPlayer = mediaplayer
}
suspend fun updateTimer() {
viewModel?.let {
while (!it.getTimer().hasEnded()) {
delay(1.seconds)
it.getTimer().tick()
if (it.getTimer().hasCurrentCountdownEnded()) {
mediaPlayer.start()
}
}
}
}
}

View file

@ -12,7 +12,7 @@ import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen
data class SessionActions( data class SessionActions(
val getTimer: () -> FunctionalTimer, val getTimer: () -> FunctionalTimer,
val getTask: () -> String, val getTask: () -> String,
val prepareMediaPlayer: () -> Unit, val startMediaPlayer: () -> Unit,
val releaseMediaPlayer: () -> Unit, val releaseMediaPlayer: () -> Unit,
val endSession: () -> Unit val endSession: () -> Unit
) )
@ -26,8 +26,8 @@ private fun getSessionActions(
getTimer = viewModel::getTimer, getTimer = viewModel::getTimer,
getTask = viewModel::getTask, getTask = viewModel::getTask,
endSession = { viewModel.endSession(openAndPopUp) }, endSession = { viewModel.endSession(openAndPopUp) },
prepareMediaPlayer = mediaplayer::prepareAsync, startMediaPlayer = mediaplayer::start,
releaseMediaPlayer = mediaplayer::release releaseMediaPlayer = mediaplayer::release,
) )
} }
@ -39,26 +39,15 @@ fun SessionRoute(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val mediaplayer = MediaPlayer() val mediaplayer = MediaPlayer.create(context, uri)
mediaplayer.setDataSource(context, uri) mediaplayer.isLooping = false
mediaplayer.setOnCompletionListener {
mediaplayer.stop()
//if (timerEnd) {
// mediaplayer.release()
//}
}
mediaplayer.setOnPreparedListener {
// mediaplayer.start()
}
val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen()) InvisibleSessionManager.setParameters(
viewModel = viewModel,
mediaplayer = mediaplayer
)
//val sessionScreen = when (val timer = viewModel.getTimer()) { val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer))
// is FunctionalCustomTimer -> CustomSessionScreen(timer)
// is FunctionalPomodoroTimer -> BreakSessionScreen(timer)
// is FunctionalEndlessTimer -> EndlessSessionScreen()
// else -> throw java.lang.IllegalArgumentException("Unknown Timer")
//}
sessionScreen( sessionScreen(
open = open, open = open,

View file

@ -9,7 +9,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextButton import androidx.compose.material.TextButton
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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
@ -19,16 +24,12 @@ 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 be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.session.SessionActions import be.ugent.sel.studeez.screens.session.SessionActions
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
abstract class AbstractSessionScreen { abstract class AbstractSessionScreen {
var timerEnd = false
@Composable @Composable
operator fun invoke( operator fun invoke(
open: (String) -> Unit, open: (String) -> Unit,
@ -75,22 +76,10 @@ abstract class AbstractSessionScreen {
LaunchedEffect(tikker) { LaunchedEffect(tikker) {
delay(1.seconds) delay(1.seconds)
sessionActions.getTimer().tick() sessionActions.getTimer().tick()
callMediaPlayer()
tikker = !tikker tikker = !tikker
} }
if (
sessionActions.getTimer().hasCurrentCountdownEnded() && !sessionActions.getTimer()
.hasEnded()
) {
// sessionActions.prepareMediaPlayer()
}
if (!timerEnd && sessionActions.getTimer().hasEnded()) {
// sessionActions.prepareMediaPlayer()
timerEnd =
true // Placeholder, vanaf hier moet het report opgestart worden en de sessie afgesloten
}
val hms = sessionActions.getTimer().getHoursMinutesSeconds() val hms = sessionActions.getTimer().getHoursMinutesSeconds()
Column { Column {
Text( Text(
@ -137,6 +126,8 @@ abstract class AbstractSessionScreen {
@Composable @Composable
abstract fun motivationString(): String abstract fun motivationString(): String
abstract fun callMediaPlayer()
} }
@Preview @Preview
@ -145,6 +136,7 @@ fun TimerPreview() {
val sessionScreen = object : AbstractSessionScreen() { val sessionScreen = object : AbstractSessionScreen() {
@Composable @Composable
override fun motivationString(): String = "Test" override fun motivationString(): String = "Test"
override fun callMediaPlayer() {}
} }
sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {})) sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {}))

View file

@ -1,5 +1,6 @@
package be.ugent.sel.studeez.screens.session.sessionScreens package be.ugent.sel.studeez.screens.session.sessionScreens
import android.media.MediaPlayer
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
@ -7,7 +8,8 @@ import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
class BreakSessionScreen( class BreakSessionScreen(
private val funPomoDoroTimer: FunctionalPomodoroTimer private val funPomoDoroTimer: FunctionalPomodoroTimer,
private var mediaplayer: MediaPlayer?
): AbstractSessionScreen() { ): AbstractSessionScreen() {
@Composable @Composable
@ -27,4 +29,17 @@ class BreakSessionScreen(
) )
} }
override fun callMediaPlayer() {
if (funPomoDoroTimer.hasEnded()) {
mediaplayer?.let { it: MediaPlayer ->
it.setOnCompletionListener {
it.release()
mediaplayer = null
}
it.start()
}
} else if (funPomoDoroTimer.hasCurrentCountdownEnded()) {
mediaplayer?.start()
}
}
} }

View file

@ -1,5 +1,6 @@
package be.ugent.sel.studeez.screens.session.sessionScreens package be.ugent.sel.studeez.screens.session.sessionScreens
import android.media.MediaPlayer
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
@ -7,7 +8,8 @@ import be.ugent.sel.studeez.R.string as AppText
class CustomSessionScreen( class CustomSessionScreen(
private val functionalTimer: FunctionalCustomTimer private val functionalTimer: FunctionalCustomTimer,
private var mediaplayer: MediaPlayer?
): AbstractSessionScreen() { ): AbstractSessionScreen() {
@Composable @Composable
@ -18,4 +20,16 @@ class CustomSessionScreen(
return resources().getString(AppText.state_focus) return resources().getString(AppText.state_focus)
} }
override fun callMediaPlayer() {
if (functionalTimer.hasEnded()) {
mediaplayer?.let { it: MediaPlayer ->
it.setOnCompletionListener {
it.release()
mediaplayer = null
}
it.start()
}
}
}
} }

View file

@ -11,4 +11,6 @@ class EndlessSessionScreen : AbstractSessionScreen() {
override fun motivationString(): String { override fun motivationString(): String {
return resources().getString(AppText.state_focus) return resources().getString(AppText.state_focus)
} }
override fun callMediaPlayer() {}
} }

View file

@ -1,17 +1,18 @@
package be.ugent.sel.studeez.screens.session.sessionScreens package be.ugent.sel.studeez.screens.session.sessionScreens
import android.media.MediaPlayer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor
class GetSessionScreen : FunctionalTimerVisitor<AbstractSessionScreen> { class GetSessionScreen(private val mediaplayer: MediaPlayer?) : FunctionalTimerVisitor<AbstractSessionScreen> {
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen = override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen =
CustomSessionScreen(functionalCustomTimer) CustomSessionScreen(functionalCustomTimer, mediaplayer)
override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen = override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen =
EndlessSessionScreen() EndlessSessionScreen()
override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen = override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen =
BreakSessionScreen(functionalPomodoroTimer) BreakSessionScreen(functionalPomodoroTimer, mediaplayer)
} }

View file

@ -26,7 +26,7 @@ fun getSessionRecapActions(
return SessionRecapActions( return SessionRecapActions(
viewModel::getSessionReport, viewModel::getSessionReport,
{viewModel.saveSession(openAndPopUp)}, {viewModel.saveSession(openAndPopUp)},
{viewModel.saveSession(openAndPopUp)} {viewModel.discardSession(openAndPopUp)}
) )
} }
@ -47,8 +47,10 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi
val sessionReport: SessionReport = sessionRecapActions.getSessionReport() val sessionReport: SessionReport = sessionRecapActions.getSessionReport()
val studyTime: Int = sessionReport.studyTime val studyTime: Int = sessionReport.studyTime
val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS()
Column { Column(
Text(text = "You studied: ${hms.hours} : ${hms.minutes} : ${hms.seconds}") modifier = modifier
) {
Text(text = "You studied: $hms")
BasicButton( BasicButton(
R.string.save, Modifier.basicButton() R.string.save, Modifier.basicButton()

View file

@ -11,7 +11,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SessionRecapViewModel @Inject constructor( class SessionRecapViewModel @Inject constructor(
private val sessionReportState: SessionReportState, sessionReportState: SessionReportState,
private val sessionDAO: SessionDAO, private val sessionDAO: SessionDAO,
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {

View file

@ -0,0 +1,42 @@
package be.ugent.sel.studeez.screens.sessions
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
@Composable
fun SessionsRoute(
// viewModel: SessionsViewModel,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions
) {
SessionsScreen(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
@Composable
fun SessionsScreen(
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions
) {
PrimaryScreenTemplate(
title = resources().getString(AppText.upcoming_sessions),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
) {
Text(
text = resources().getString(AppText.sessions_temp_description),
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center
)
}
}

View file

@ -0,0 +1,37 @@
package be.ugent.sel.studeez.screens.settings
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import be.ugent.sel.studeez.common.composable.DrawerScreenTemplate
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
@Composable
fun SettingsRoute(
// viewModel: SettingsViewModel,
drawerActions: DrawerActions
) {
SettingsScreen(
drawerActions = drawerActions
)
}
@Composable
fun SettingsScreen(
drawerActions: DrawerActions
) {
DrawerScreenTemplate(
title = resources().getString(AppText.settings),
drawerActions = drawerActions
) {
Text(
text = resources().getString(AppText.settings_temp_description),
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center
)
}
}

View file

@ -72,7 +72,7 @@ fun SubjectScreen(
fun SubjectScreenPreview() { fun SubjectScreenPreview() {
SubjectScreen( SubjectScreen(
drawerActions = DrawerActions({}, {}, {}, {}, {}), drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
addSubject = {}, addSubject = {},
getSubjects = { flowOf() }, getSubjects = { flowOf() },
onViewSubject = {}, onViewSubject = {},

View file

@ -0,0 +1,44 @@
package be.ugent.sel.studeez.screens.timer_add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.screens.timer_edit.GetTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.TimerEditViewModel
import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen
import be.ugent.sel.studeez.ui.theme.StudeezTheme
data class TimerEditActions(
val getTimerInfo: () -> TimerInfo,
val saveTimer: (TimerInfo, () -> Unit) -> Unit
)
fun getTimerEditActions(
viewModel: TimerEditViewModel,
open: (String) -> Unit
): TimerEditActions {
return TimerEditActions(
getTimerInfo = viewModel::getTimerInfo,
saveTimer = viewModel::saveTimer
)
}
@Composable
fun TimerEditRoute(
open: (String) -> Unit,
popUp: () -> Unit,
viewModel: TimerEditViewModel,
) {
val timerEditActions = getTimerEditActions(viewModel, open)
SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) {
val timerEditScreen = timerEditActions.getTimerInfo().accept(GetTimerEditScreen())
timerEditScreen { timerInfo ->
timerEditActions.saveTimer(timerInfo, popUp)
}
}
}

View file

@ -0,0 +1,29 @@
package be.ugent.sel.studeez.screens.timer_add
import be.ugent.sel.studeez.data.EditTimerState
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
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 javax.inject.Inject
@HiltViewModel
class TimerAddViewModel @Inject constructor(
private val editTimerState: EditTimerState,
private val timerDAO: TimerDAO,
logService: LogService
) : StudeezViewModel(logService) {
private val timerInfo: TimerInfo = editTimerState.timerInfo
fun getTimerInfo(): TimerInfo {
return timerInfo
}
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
timerDAO.updateTimer(timerInfo)
goBack()
}
}

View file

@ -0,0 +1,43 @@
package be.ugent.sel.studeez.screens.timer_add
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.data.local.models.timer_info.*
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.CUSTOM
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.BREAK
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS
val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf(
CUSTOM to CustomTimerInfo("", "", 0),
BREAK to PomodoroTimerInfo("", "", 0, 0, 0),
ENDLESS to EndlessTimerInfo("", ""),
)
@Composable
fun TimerTypeSelectScreen(
open: (String) -> Unit,
popUp: () -> Unit,
viewModel: TimerTypeSelectViewModel = hiltViewModel()
) {
SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
TimerType.values().forEach { timerType ->
Button(onClick = { viewModel.onTimerTypeChosen(defaultTimerInfo[timerType]!!, open) }) {
Text(text = timerType.name)
}
}
}
}
}

View file

@ -0,0 +1,25 @@
package be.ugent.sel.studeez.screens.timer_add
import be.ugent.sel.studeez.data.EditTimerState
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.TimerDAO
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@HiltViewModel
class TimerTypeSelectViewModel @Inject constructor(
private val editTimerState: EditTimerState,
private val timerDAO: TimerDAO,
logService: LogService
) : StudeezViewModel(logService) {
fun onTimerTypeChosen(timerInfo: TimerInfo, open: (String) -> Unit) {
editTimerState.timerInfo = timerInfo
open(StudeezDestinations.TIMER_EDIT_SCREEN)
}
}

View file

@ -0,0 +1,27 @@
package be.ugent.sel.studeez.screens.timer_edit
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfoVisitor
import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.editScreens.BreakTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.editScreens.CustomTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.editScreens.EndlessTimerEditScreen
class GetTimerEditScreen: TimerInfoVisitor<AbstractTimerEditScreen> {
override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): AbstractTimerEditScreen {
return CustomTimerEditScreen(customTimerInfo)
}
override fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): AbstractTimerEditScreen {
return EndlessTimerEditScreen(endlessTimerInfo)
}
override fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): AbstractTimerEditScreen {
return BreakTimerEditScreen(pomodoroTimerInfo)
}
}

View file

@ -0,0 +1,65 @@
package be.ugent.sel.studeez.screens.timer_edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen
import be.ugent.sel.studeez.ui.theme.StudeezTheme
data class TimerEditActions(
val getTimerInfo: () -> TimerInfo,
val saveTimer: (TimerInfo, () -> Unit) -> Unit
)
fun getTimerEditActions(
viewModel: TimerEditViewModel,
open: (String) -> Unit
): TimerEditActions {
return TimerEditActions(
getTimerInfo = viewModel::getTimerInfo,
saveTimer = viewModel::saveTimer
)
}
@Composable
fun TimerEditRoute(
open: (String) -> Unit,
popUp: () -> Unit,
viewModel: TimerEditViewModel,
) {
val timerEditActions = getTimerEditActions(viewModel, open)
SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) {
val timerEditScreen = timerEditActions.getTimerInfo().accept(GetTimerEditScreen())
timerEditScreen { timerInfo ->
timerEditActions.saveTimer(timerInfo, popUp)
}
}
}

View file

@ -0,0 +1,29 @@
package be.ugent.sel.studeez.screens.timer_edit
import be.ugent.sel.studeez.data.EditTimerState
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
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 javax.inject.Inject
@HiltViewModel
class TimerEditViewModel @Inject constructor(
private val editTimerState: EditTimerState,
private val timerDAO: TimerDAO,
logService: LogService
) : StudeezViewModel(logService) {
private val timerInfo: TimerInfo = editTimerState.timerInfo
fun getTimerInfo(): TimerInfo {
return timerInfo
}
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
timerDAO.updateTimer(timerInfo)
goBack()
}
}

View file

@ -0,0 +1,71 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.LabelledInputField
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) {
@Composable
operator fun invoke(onSaveClick: (TimerInfo) -> Unit) {
var name by remember { mutableStateOf(timerInfo.name) }
var description by remember { mutableStateOf(timerInfo.description) }
// This shall rerun whenever name and description change
timerInfo.name = name
timerInfo.description = description
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxHeight().verticalScroll(rememberScrollState()),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Fields that every timer shares (ommited id)
LabelledInputField(
value = name,
onNewValue = { name = it },
label = R.string.name
)
LabelledInputField(
value = description,
onNewValue = { description = it },
label = R.string.description,
singleLine = false
)
ExtraFields()
}
BasicButton(R.string.save, Modifier.basicButton()) {
onSaveClick(timerInfo)
}
}
}
@Composable
open fun ExtraFields() {
// By default no extra fields, unless overwritten by subclass.
}
}

View file

@ -0,0 +1,46 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.TimePickerButton
import be.ugent.sel.studeez.common.composable.TimePickerCard
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import be.ugent.sel.studeez.data.local.models.timer_functional.Time
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo
import be.ugent.sel.studeez.ui.theme.StudeezTheme
class BreakTimerEditScreen(
private val breakTimerInfo: PomodoroTimerInfo
): AbstractTimerEditScreen(breakTimerInfo) {
@Composable
override fun ExtraFields() {
// If the user presses the OK button on the timepicker, the time in the button should change
TimePickerCard(R.string.studyTime, breakTimerInfo.studyTime) { newTime ->
breakTimerInfo.studyTime = newTime
}
TimePickerCard(R.string.breakTime, breakTimerInfo.breakTime) { newTime ->
breakTimerInfo.breakTime = newTime
}
}
}
@Preview
@Composable
fun BreakEditScreenPreview() {
val pomodoroTimerInfo = PomodoroTimerInfo(
"Breaky the Breaktimer",
"Breaky is a breakdancer",
10 * 60,
60,
5
)
StudeezTheme {
BreakTimerEditScreen(pomodoroTimerInfo).invoke(onSaveClick = {})
}
}

View file

@ -0,0 +1,34 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.TimePickerCard
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.ui.theme.StudeezTheme
import be.ugent.sel.studeez.R.string as AppText
class CustomTimerEditScreen(
private val customTimerInfo: CustomTimerInfo
): AbstractTimerEditScreen(customTimerInfo) {
@Composable
override fun ExtraFields() {
TimePickerCard(
text = AppText.studyTime,
initialSeconds = customTimerInfo.studyTime
) { newTime ->
customTimerInfo.studyTime = newTime
}
}
}
@Preview
@Composable
fun CustomEditScreenPreview() {
val customTimerInfo = CustomTimerInfo("custom", "my description", 25)
StudeezTheme {
CustomTimerEditScreen(customTimerInfo).invoke(onSaveClick = {})
}
}

View file

@ -0,0 +1,23 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.ui.theme.StudeezTheme
class EndlessTimerEditScreen(
endlessTimerInfo: EndlessTimerInfo
): AbstractTimerEditScreen(endlessTimerInfo) {
}
@Preview
@Composable
fun EndlessEditScreenPreview() {
val endlessTimerInfo = EndlessTimerInfo(
"Forever and beyond",
"My endless timer description",
)
StudeezTheme {
EndlessTimerEditScreen(endlessTimerInfo).invoke(onSaveClick = {})
}
}

View file

@ -16,6 +16,7 @@ import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo 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.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
@ -24,15 +25,18 @@ data class TimerOverviewActions(
val getUserTimers: () -> Flow<List<TimerInfo>>, val getUserTimers: () -> Flow<List<TimerInfo>>,
val getDefaultTimers: () -> List<TimerInfo>, val getDefaultTimers: () -> List<TimerInfo>,
val onEditClick: (TimerInfo) -> Unit, val onEditClick: (TimerInfo) -> Unit,
val onAddClick: () -> Unit,
) )
fun getTimerOverviewActions( fun getTimerOverviewActions(
viewModel: TimerOverviewViewModel, viewModel: TimerOverviewViewModel,
open: (String) -> Unit,
): TimerOverviewActions { ): TimerOverviewActions {
return TimerOverviewActions( return TimerOverviewActions(
getUserTimers = viewModel::getUserTimers, getUserTimers = viewModel::getUserTimers,
getDefaultTimers = viewModel::getDefaultTimers, getDefaultTimers = viewModel::getDefaultTimers,
onEditClick = { viewModel.update(it) }, onEditClick = { viewModel.update(it, open) },
onAddClick = { viewModel.create(open) }
) )
} }
@ -40,10 +44,11 @@ fun getTimerOverviewActions(
fun TimerOverviewRoute( fun TimerOverviewRoute(
viewModel: TimerOverviewViewModel, viewModel: TimerOverviewViewModel,
drawerActions: DrawerActions, drawerActions: DrawerActions,
open: (String) -> Unit
) { ) {
TimerOverviewScreen( TimerOverviewScreen(
timerOverviewActions = getTimerOverviewActions(viewModel), timerOverviewActions = getTimerOverviewActions(viewModel, open),
drawerActions = drawerActions, drawerActions = drawerActions
) )
} }
@ -59,8 +64,16 @@ fun TimerOverviewScreen(
title = resources().getString(R.string.timers), title = resources().getString(R.string.timers),
drawerActions = drawerActions drawerActions = drawerActions
) { ) {
Column { Column { // TODO knop beneden
LazyColumn { LazyColumn {
// Custom timer, select new duration each time
item {
TimerEntry(timerInfo = CustomTimerInfo(
name = resources().getString(R.string.custom_name),
description = resources().getString(R.string.custom_name),
studyTime = 0
))
}
// Default Timers, cannot be edited // Default Timers, cannot be edited
items(timerOverviewActions.getDefaultTimers()) { items(timerOverviewActions.getDefaultTimers()) {
TimerEntry(timerInfo = it) {} TimerEntry(timerInfo = it) {}
@ -77,9 +90,13 @@ fun TimerOverviewScreen(
} }
} }
}
BasicButton(R.string.add_timer, Modifier.basicButton()) { // TODO uit lazy column
// TODO item {
BasicButton(R.string.add_timer, Modifier.basicButton()) {
timerOverviewActions.onAddClick()
}
}
} }
} }
} }
@ -95,7 +112,9 @@ fun TimerOverviewPreview() {
timerOverviewActions = TimerOverviewActions( timerOverviewActions = TimerOverviewActions(
{ flowOf() }, { flowOf() },
{ listOf(customTimer, customTimer) }, { listOf(customTimer, customTimer) },
{}), {},
{}
),
drawerActions = DrawerActions({}, {}, {}, {}, {}) drawerActions = DrawerActions({}, {}, {}, {}, {})
) )
} }

View file

@ -1,9 +1,11 @@
package be.ugent.sel.studeez.screens.timer_overview package be.ugent.sel.studeez.screens.timer_overview
import be.ugent.sel.studeez.data.EditTimerState
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.domain.ConfigurationService import be.ugent.sel.studeez.domain.ConfigurationService
import be.ugent.sel.studeez.domain.LogService import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.TimerDAO import be.ugent.sel.studeez.domain.TimerDAO
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -13,6 +15,7 @@ import javax.inject.Inject
class TimerOverviewViewModel @Inject constructor( class TimerOverviewViewModel @Inject constructor(
private val configurationService: ConfigurationService, private val configurationService: ConfigurationService,
private val timerDAO: TimerDAO, private val timerDAO: TimerDAO,
private val editTimerState: EditTimerState,
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
@ -24,7 +27,14 @@ class TimerOverviewViewModel @Inject constructor(
return configurationService.getDefaultTimers() return configurationService.getDefaultTimers()
} }
fun update(timerInfo: TimerInfo) = timerDAO.updateTimer(timerInfo) fun update(timerInfo: TimerInfo, open: (String) -> Unit) {
editTimerState.timerInfo = timerInfo
open(StudeezDestinations.TIMER_EDIT_SCREEN)
}
fun create(open: (String) -> Unit) {
open(StudeezDestinations.ADD_TIMER_SCREEN)
}
fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo) fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo)

View file

@ -0,0 +1,12 @@
package be.ugent.sel.studeez.screens.timer_overview.add_timer
data class AddTimerUiState(
val studyTimeHours: Int = 1,
val studyTimeMinutes: Int = 0,
val withBreaks: Boolean = false,
val breakTimeMinutes: Int = 5,
val breakTimeHours: Int = 0,
val repeats: Int = 1,
val name: String = "Timer",
val description: String = "Long study session",
)

View file

@ -0,0 +1,91 @@
package be.ugent.sel.studeez.screens.timer_overview.add_timer
import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo
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 javax.inject.Inject
@HiltViewModel
class AddTimerViewModel @Inject constructor(
logService: LogService,
private val timerDAO: TimerDAO,
): StudeezViewModel(logService) {
var uiState = mutableStateOf(AddTimerUiState())
private set
private val studyTimeHours
get() = uiState.value.studyTimeHours
private val studyTimeMinutes
get() = uiState.value.studyTimeMinutes
private val breakTimeHours
get() = uiState.value.breakTimeHours
private val breakTimeMinutes
get() = uiState.value.breakTimeMinutes
private val repeats
get() = uiState.value.repeats
private val name
get() = uiState.value.name
private val description
get() = uiState.value.description
fun onStudyTimeHoursChange(newValue: Int) {
uiState.value = uiState.value.copy(studyTimeHours = newValue)
}
fun onStudyTimeMinutesChange(newValue: Int) {
uiState.value = uiState.value.copy(studyTimeMinutes = newValue)
}
fun onWithBreaksChange() {
uiState.value = uiState.value.copy(withBreaks = !uiState.value.withBreaks)
}
fun onBreakTimeHourChange(newValue: Int) {
uiState.value = uiState.value.copy(breakTimeHours = newValue)
}
fun onBreakTimeMinutesChange(newValue: Int) {
uiState.value = uiState.value.copy(breakTimeMinutes = newValue)
}
fun onRepeatsChange(newValue: Int) {
uiState.value = uiState.value.copy(repeats = newValue)
}
fun addTimer() {
if (uiState.value.withBreaks) {
timerDAO.saveTimer(PomodoroTimerInfo(
name = uiState.value.name,
description = uiState.value.description,
studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60,
breakTime = breakTimeHours * 60 * 60 + breakTimeMinutes * 60,
repeats = repeats
))
} else {
timerDAO.saveTimer(CustomTimerInfo(
name = uiState.value.name,
description = uiState.value.description,
studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60
))
}
}
fun onNameChange(newValue: String) {
uiState.value = uiState.value.copy(name = newValue)
}
fun onDescriptionChange(newValue: String) {
uiState.value = uiState.value.copy(description = newValue)
}
}

View file

@ -0,0 +1,274 @@
package be.ugent.sel.studeez.screens.timer_overview.add_timer
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.navbar.BasicTimePicker
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme
data class AddTimerActions(
val open: (String) -> Unit,
val goBack: () -> Unit,
val onStudyTimeHoursChange: (Int) -> Unit,
val onStudyTimeMinutesChange: (Int) -> Unit,
val onBreakTimeHourChange: (Int) -> Unit,
val onBreakTimeMinutesChange: (Int) -> Unit,
val onRepeatsChange: (Int) -> Unit,
val onWithBreaksChange: () -> Unit,
val addTimer: () -> Unit,
val onNameChange: (String) -> Unit,
val onDescriptionChange: (String) -> Unit,
)
fun getAddTimerActions(
open: (String) -> Unit,
goBack: () -> Unit,
viewModel: AddTimerViewModel,
): AddTimerActions {
return AddTimerActions(
open = open,
goBack = goBack,
onWithBreaksChange = viewModel::onWithBreaksChange,
onStudyTimeHoursChange = viewModel::onStudyTimeHoursChange,
onStudyTimeMinutesChange = viewModel::onStudyTimeMinutesChange,
onBreakTimeHourChange = viewModel::onBreakTimeHourChange,
onBreakTimeMinutesChange = viewModel::onBreakTimeMinutesChange,
onRepeatsChange = viewModel::onRepeatsChange,
addTimer = viewModel::addTimer,
onNameChange = viewModel::onNameChange,
onDescriptionChange = viewModel::onDescriptionChange
)
}
@Composable
fun AddTimerRoute(
open: (String) -> Unit,
goBack: () -> Unit,
viewModel: AddTimerViewModel,
) {
val uiState by viewModel.uiState
AddTimerScreen(
addTimerActions = getAddTimerActions(
open = open,
goBack = goBack,
viewModel = viewModel,
),
uiState = uiState
)
}
@Composable
fun AddTimerScreen(
addTimerActions: AddTimerActions,
uiState: AddTimerUiState,
) {
val mStudyTimePicker = BasicTimePicker(
onHoursChange = addTimerActions.onStudyTimeHoursChange,
onMinutesChange = addTimerActions.onStudyTimeMinutesChange,
Hours = uiState.studyTimeHours,
Minutes = uiState.studyTimeMinutes
)
val mBreakTimePicker = BasicTimePicker(
onHoursChange = addTimerActions.onBreakTimeHourChange,
onMinutesChange = addTimerActions.onBreakTimeMinutesChange,
Hours = uiState.breakTimeHours,
Minutes = uiState.breakTimeMinutes
)
SecondaryScreenTemplate(
title = resources().getString(R.string.add_timer),
popUp = addTimerActions.goBack
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
Row(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.addTimer_question),
textAlign = TextAlign.Center
)
}
}
item {
Text(
text = uiState.studyTimeHours.toString() + stringResource(R.string.addTimer_studytime_1) + uiState.studyTimeMinutes + stringResource(
R.string.addTimer_studytime_2)
)
}
item {
Button(
onClick = {
mStudyTimePicker.show()
},
) {
Text(
text = stringResource(R.string.addTimer_timepicker),
)
}
}
item {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.addTimer_break_question),
)
Checkbox(
checked = uiState.withBreaks,
onCheckedChange = { addTimerActions.onWithBreaksChange() }
)
}
}
if (uiState.withBreaks) {
item {
Text(
text = if (uiState.repeats == 1) uiState.repeats.toString() + stringResource(
R.string.addTimer_break_1)
else uiState.repeats.toString() + stringResource(
R.string.addTimer_break_s)
)
TextField(
value = uiState.repeats.toString(),
onValueChange = { it: String ->
it.toIntOrNull()?.let { it1 ->
addTimerActions.onRepeatsChange(
kotlin.math.abs(it1)
)
}
}
)
}
item {
Text(
text = uiState.breakTimeHours.toString() + stringResource(R.string.breakTime_1) + uiState.breakTimeMinutes + stringResource(
R.string.breakTime_2)
)
}
item {
Button(
onClick = {
mBreakTimePicker.show()
},
) {
Text(
text = stringResource(R.string.addTimer_timepicker),
)
}
}
}
item {
Text(
text = stringResource(R.string.addTimer_name)
)
}
item {
TextField(
value = uiState.name,
onValueChange = { addTimerActions.onNameChange(it) }
)
}
item {
if (uiState.name == "") {
Text(
text = stringResource(R.string.addTimer_name_error),
color = Color.Red
)
}
}
item {
Text(
text = stringResource(R.string.addTimer_description)
)
}
item {
TextField(
value = uiState.description,
onValueChange = { addTimerActions.onDescriptionChange(it) }
)
}
item {
if (uiState.description == "") {
Text(
text = stringResource(R.string.addTimer_description_error),
color = Color.Red
)
}
}
item {
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.Center
) {
BasicButton(
text = R.string.add_timer,
modifier = Modifier,
onClick = {
if (uiState.description != "" && uiState.name != "") {
addTimerActions.addTimer()
addTimerActions.open(StudeezDestinations.TIMER_SCREEN)
}
}
)
}
}
}
}
}
@Preview
@Composable
fun AddTimerScreenPreview() { StudeezTheme {
AddTimerScreen(
addTimerActions = AddTimerActions({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}),
uiState = AddTimerUiState()
)
}
}

View file

@ -8,7 +8,11 @@ import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.StealthButton import be.ugent.sel.studeez.common.composable.StealthButton
import be.ugent.sel.studeez.common.composable.TimePickerButton
import be.ugent.sel.studeez.common.composable.TimerEntry import be.ugent.sel.studeez.common.composable.TimerEntry
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import be.ugent.sel.studeez.data.local.models.timer_functional.Time
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.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -17,6 +21,7 @@ import kotlinx.coroutines.flow.flowOf
data class TimerSelectionActions( data class TimerSelectionActions(
val getAllTimers: () -> Flow<List<TimerInfo>>, val getAllTimers: () -> Flow<List<TimerInfo>>,
val startSession: (TimerInfo) -> Unit, val startSession: (TimerInfo) -> Unit,
val customTimeStudyTime: Int
) )
fun getTimerSelectionActions( fun getTimerSelectionActions(
@ -26,6 +31,7 @@ fun getTimerSelectionActions(
return TimerSelectionActions( return TimerSelectionActions(
getAllTimers = viewModel::getAllTimers, getAllTimers = viewModel::getAllTimers,
startSession = { viewModel.startSession(open, it) }, startSession = { viewModel.startSession(open, it) },
customTimeStudyTime = viewModel.customTimerStudyTime.value
) )
} }
@ -52,6 +58,11 @@ fun TimerSelectionScreen(
popUp = popUp popUp = popUp
) { ) {
LazyColumn { LazyColumn {
// Custom timer with duration selection button
item {
CustomTimerEntry(timerSelectionActions)
}
// All timers // All timers
items(timers.value) { timerInfo -> items(timers.value) { timerInfo ->
TimerEntry( TimerEntry(
@ -68,11 +79,38 @@ fun TimerSelectionScreen(
} }
} }
@Composable
fun CustomTimerEntry(
timerSelectionActions: TimerSelectionActions
) {
val timerInfo = CustomTimerInfo(
name = resources().getString(R.string.custom_name),
description = resources().getString(R.string.custom_description),
studyTime = timerSelectionActions.customTimeStudyTime
)
val hms: HoursMinutesSeconds = Time(timerInfo.studyTime).getAsHMS()
TimerEntry(
timerInfo = timerInfo,
leftButton = {
StealthButton(
text = R.string.start,
onClick = { timerSelectionActions.startSession(timerInfo) }
)
},
rightButton = {
TimePickerButton(initialSeconds = hms.getTotalSeconds()) { chosenTime ->
timerInfo.studyTime = chosenTime
}
}
)
}
@Preview @Preview
@Composable @Composable
fun TimerSelectionPreview() { fun TimerSelectionPreview() {
TimerSelectionScreen( TimerSelectionScreen(
timerSelectionActions = TimerSelectionActions({ flowOf() }, {}), timerSelectionActions = TimerSelectionActions({ flowOf() }, {}, 0),
popUp = {} popUp = {}
) )
} }

View file

@ -1,5 +1,9 @@
package be.ugent.sel.studeez.screens.timer_selection package be.ugent.sel.studeez.screens.timer_selection
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import be.ugent.sel.studeez.data.SelectedTimerState import be.ugent.sel.studeez.data.SelectedTimerState
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.domain.LogService import be.ugent.sel.studeez.domain.LogService
@ -17,6 +21,8 @@ class TimerSelectionViewModel @Inject constructor(
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var customTimerStudyTime: MutableState<Int> = mutableStateOf(0)
fun getAllTimers() : Flow<List<TimerInfo>> { fun getAllTimers() : Flow<List<TimerInfo>> {
return timerDAO.getAllTimers() return timerDAO.getAllTimers()
} }

View file

@ -12,7 +12,9 @@ import androidx.compose.ui.graphics.Color
private val DarkColorPalette = darkColors( private val DarkColorPalette = darkColors(
primary = Blue100, primary = Blue100,
primaryVariant = Blue120, primaryVariant = Blue120,
secondary = Yellow100 secondary = Yellow100,
onPrimary = Color.White
) )
private val LightColorPalette = lightColors( private val LightColorPalette = lightColors(

View file

@ -1,5 +1,6 @@
<resources> <resources>
<!-- Common --> <!-- ========== Common ========== -->
<string name="app_name">Studeez</string> <string name="app_name">Studeez</string>
<string name="username">Username</string> <string name="username">Username</string>
<string name="email">Email</string> <string name="email">Email</string>
@ -7,20 +8,83 @@
<string name="repeat_password">Repeat password</string> <string name="repeat_password">Repeat password</string>
<string name="menu">Menu</string> <string name="menu">Menu</string>
<!-- Actions --> <!-- Actions -->
<string name="confirm">Confirm</string> <string name="confirm">Confirm</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="discard">Discard</string> <string name="discard">Discard</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="go_back">Go back</string> <string name="go_back">Go back</string>
<string name="next">Next</string> <string name="next">Next</string>
<string name="start">Start</string> <string name="start">Start</string>
<!-- Messages --> <!-- Messages -->
<string name="success">Success!</string> <string name="success">Success!</string>
<string name="try_again">Try again</string> <string name="try_again">Try again</string>
<string name="generic_error">Something wrong happened. Please try again.</string> <string name="generic_error">Something wrong happened. Please try again.</string>
<string name="email_error">Please insert a valid email.</string> <string name="email_error">Please insert a valid email.</string>
<!-- ========== NavBar ========== -->
<!-- HomeScreen -->
<string name="home">Home</string>
<string name="start_session">Start session</string>
<!-- Tasks -->
<string name="tasks">Tasks</string>
<string name="task">Task</string>
<string name="my_subjects">My Subjects</string>
<string name="new_subject">New Subject</string>
<string name="new_task">New Task</string>
<string name="edit_subject">Edit Subject</string>
<string name="edit_task">Edit Task</string>
<string name="delete_subject">Delete Subject</string>
<string name="delete_task">Delete Task</string>
<string name="view_tasks">View</string>
<!-- Sessions -->
<string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. -->
<string name="sessions">Sessions</string>
<string name="session">Session</string>
<string name="end_session">End session</string>
<string name="upcoming_sessions">Upcoming sessions</string>
<!-- Profile -->
<string name="profile">Profile</string>
<string name="no_username">Unknown username</string>
<string name="edit_profile">Edit profile</string>
<string name="editing_profile">Editing profile</string>
<string name="delete_profile">Delete profile</string>
<!-- ========== Drawer ========== -->
<string name="log_out">Log out</string>
<string name="profile_picture_description">Profile Picture</string>
<string name="user_description">Normal user</string>
<!-- Timers -->
<string name="timers">Timers</string>
<string name="edit">Edit</string>
<string name="add_timer">Add timer</string>
<string name="pick_time">Select time</string>
<string name="state_focus">Focus!</string>
<plurals name="state_focus_remaining">
<item quantity="zero">Focus one more time!</item>
<item quantity="one">Focus! (%d break remaining)</item>
<item quantity="other">Focus! (%d breaks remaining)</item>
</plurals>
<string name="state_done">Done!</string>
<string name="state_take_a_break">Take a break!</string>
<string name="custom_name">Custom</string>
<string name="custom_description">Select how long you want to study</string>
<!-- Settings -->
<string name="settings_temp_description">Looks like you found the settings screen! In the future, this will enable you to edit your preferenes such as light/dark mode, end sessions automatically when we detect you are gone etc.</string> <!-- TODO Remove this description line once implemented. -->
<string name="settings">Settings</string>
<!-- About -->
<string name="about">About Studeez</string>
<!-- ========== Login flow ========== -->
<!-- SignUpScreen --> <!-- SignUpScreen -->
<string name="create_account">Create account</string> <string name="create_account">Create account</string>
@ -36,58 +100,42 @@
<string name="recovery_email_sent">Check your inbox for the recovery email.</string> <string name="recovery_email_sent">Check your inbox for the recovery email.</string>
<string name="empty_password_error">Password cannot be empty.</string> <string name="empty_password_error">Password cannot be empty.</string>
<!-- HomeScreen --> <!-- ========== Studying flow ========== -->
<string name="home">Home</string>
<string name="start_session">Start session</string>
<!-- Tasks --> <!-- ========== Friends flow ========== -->
<string name="tasks">Tasks</string>
<string name="my_subjects">My Subjects</string>
<string name="new_subject">New Subject</string>
<string name="new_task">New Task</string>
<string name="edit_subject">Edit Subject</string>
<string name="edit_task">Edit Task</string>
<string name="delete_subject">Delete Subject</string>
<string name="delete_task">Delete Task</string>
<string name="view_tasks">View</string>
<!-- Form -->
<string name="name">Name</string>
<!-- Sessions -->
<string name="sessions">Sessions</string>
<!-- Profile -->
<string name="profile">Profile</string>
<string name="no_username">Unknown username</string>
<string name="edit_profile">Edit profile</string>
<string name="editing_profile">Editing profile</string>
<string name="delete_profile">Delete profile</string>
<!-- Friends -->
<string name="friends">Friends</string> <string name="friends">Friends</string>
<string name="friend">Friend</string>
<string name="add_friend_not_possible_yet">Adding friends still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. -->
<!-- Drawer / SideMenu --> <!-- ========== Create & edit screens ========== -->
<string name="log_out">Log out</string>
<string name="profile_picture_description">Profile Picture</string>
<string name="user_description">Normal user</string>
<!-- Timers --> <!-- Task -->
<string name="timers">Timers</string> <string name="create_task_not_possible_yet">Creating tasks still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. -->
<string name="edit">Edit</string>
<string name="add_timer">Add timer</string>
<string name="state_focus">Focus!</string>
<plurals name="state_focus_remaining">
<item quantity="zero">Focus one more time!</item>
<item quantity="one">Focus! (%d break remaining)</item>
<item quantity="other">Focus! (%d breaks remaining)</item>
</plurals>
<string name="state_done">Done!</string>
<string name="state_take_a_break">Take a break!</string>
<!-- Settings --> <!-- Session -->
<string name="settings">Settings</string> <string name="create_session_not_possible_yet">Creating sessions still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. -->
<!-- Add Timer -->
<string name="addTimer_description_error">Timer description cannot be empty!</string>
<string name="addTimer_description">Timer description</string>
<string name="addTimer_name_error">Timer name cannot be empty!</string>
<string name="addTimer_name">Timer name</string>
<string name="addTimer_timepicker">Open Time Picker</string>
<string name="breakTime_1">" hours and "</string>
<string name="breakTime_2">" minutes of breaktime"</string>
<string name="addTimer_break_1">" break"</string>
<string name="addTimer_break_s">" breaks"</string>
<string name="addTimer_break_question">With breaks?</string>
<string name="addTimer_studytime_1">" hours and "</string>
<string name="addTimer_studytime_2">" minutes of studytime"</string>
<string name="addTimer_question">How long do you want to study?</string>
<!-- Edit Timer-->
<string name="name">Name</string>
<string name="description">Description</string>
<string name="studyTime">Study Time</string>
<string name="breakTime">Break Time</string>
<string name="repeats">Number of Repeats</string>
<!-- About -->
<string name="about">About Studeez</string>
</resources> </resources>

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.timer_functional package be.ugent.sel.studeez.timer_functional
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.timer_functional package be.ugent.sel.studeez.timer_functional
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.timer_functional package be.ugent.sel.studeez.timer_functional
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test

View file

@ -0,0 +1,100 @@
package be.ugent.sel.studeez.timer_functional
import android.media.MediaPlayer
import be.ugent.sel.studeez.data.SelectedTimerState
import be.ugent.sel.studeez.data.SessionReportState
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.screens.session.InvisibleSessionManager
import be.ugent.sel.studeez.screens.session.SessionViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Test
import org.mockito.kotlin.mock
@ExperimentalCoroutinesApi
class InvisibleSessionManagerTest {
private var timerState: SelectedTimerState = SelectedTimerState()
private lateinit var viewModel: SessionViewModel
private var mediaPlayer: MediaPlayer = mock()
@Test
fun InvisibleEndlessTimerTest() = runTest {
timerState.selectedTimer = FunctionalEndlessTimer()
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch {
InvisibleSessionManager.updateTimer()
}
Assert.assertEquals(viewModel.getTimer().time.time, 0)
advanceTimeBy(1_000) // Start tikker
advanceTimeBy(10_000_000)
Assert.assertEquals(viewModel.getTimer().time.time, 10000)
test.cancel()
return@runTest
}
@Test
fun InvisiblePomodoroTimerTest() = runTest {
val studyTime = 10
val breakTime = 5
val repeats = 1
timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats)
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch {
InvisibleSessionManager.updateTimer()
}
Assert.assertEquals(viewModel.getTimer().time.time, 10)
advanceTimeBy(1_000) // start tikker
advanceTimeBy(9_000)
Assert.assertEquals(viewModel.getTimer().time.time, 1)
// focus, 9 sec, 1 sec nog
advanceTimeBy(2_000)
Assert.assertEquals(viewModel.getTimer().time.time, 4)
// pauze, 11 sec bezig, 4 seconden nog pauze
advanceTimeBy(5_000)
Assert.assertEquals(viewModel.getTimer().time.time, 9)
// 2e focus, 16 sec, 9 sec in 2e focus nog
advanceTimeBy(13_000)
Assert.assertTrue(viewModel.getTimer().hasEnded())
// Done
test.cancel()
return@runTest
}
@Test
fun InvisibleCustomTimerTest() = runTest {
timerState.selectedTimer = FunctionalCustomTimer(5)
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch {
InvisibleSessionManager.updateTimer()
}
Assert.assertEquals(viewModel.getTimer().time.time, 5)
advanceTimeBy(1_000) // Start tikker
advanceTimeBy(4_000)
Assert.assertEquals(viewModel.getTimer().time.time, 1)
advanceTimeBy(1_000)
Assert.assertEquals(viewModel.getTimer().time.time, 0)
test.cancel()
return@runTest
}
}

View file

@ -7,6 +7,8 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.daemon=true
org.gradle.parallel=true
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects