diff --git a/app/build.gradle b/app/build.gradle index 5dc6aa5..a19cbd7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -66,6 +66,7 @@ dependencies { implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.compose.material:material:1.2.0' + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" // ViewModel @@ -97,6 +98,9 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' + // Coroutine testing + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' + // Mocking testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0' diff --git a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt index 0c8ee3c..11454ff 100644 --- a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt +++ b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt @@ -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.session.SessionRoute 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.splash.SplashRoute 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.TaskAddRoute 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.add_timer.AddTimerRoute import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute import be.ugent.sel.studeez.ui.theme.StudeezTheme import kotlinx.coroutines.CoroutineScope @@ -97,7 +101,7 @@ fun resources(): Resources { @Composable fun StudeezNavGraph( appState: StudeezAppstate, - modifier: Modifier, + modifier: Modifier = Modifier, ) { val drawerViewModel: DrawerViewModel = hiltViewModel() val navBarViewModel: NavigationBarViewModel = hiltViewModel() @@ -119,35 +123,13 @@ fun StudeezNavGraph( startDestination = StudeezDestinations.SPLASH_SCREEN, modifier = modifier, ) { - - - 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(), - ) - } - + // NavBar composable(StudeezDestinations.HOME_SCREEN) { HomeRoute( open, viewModel = hiltViewModel(), 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) { TaskAddRoute( goBack = goBack, @@ -192,16 +182,14 @@ fun StudeezNavGraph( ) } - composable(StudeezDestinations.TASKS_SCREEN) { - TaskRoute( - goBack = goBack, - open = open, - viewModel = hiltViewModel(), + + composable(StudeezDestinations.SESSIONS_SCREEN) { + SessionsRoute( + drawerActions = drawerActions, + navigationBarActions = navigationBarActions ) } - // TODO Sessions screen - composable(StudeezDestinations.PROFILE_SCREEN) { ProfileRoute( open, @@ -211,10 +199,49 @@ fun StudeezNavGraph( ) } - composable(StudeezDestinations.TIMER_OVERVIEW_SCREEN) { + // Drawer + composable(StudeezDestinations.TIMER_SCREEN) { TimerOverviewRoute( viewModel = hiltViewModel(), 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 - // TODO Settings screen + composable(StudeezDestinations.SESSION_RECAP) { + 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) { EditProfileRoute( goBack, @@ -237,20 +297,5 @@ fun StudeezNavGraph( viewModel = hiltViewModel(), ) } - - composable(StudeezDestinations.TIMER_SELECTION_SCREEN) { - TimerSelectionRoute( - open, - goBack, - viewModel = hiltViewModel(), - ) - } - - composable(StudeezDestinations.SESSION_RECAP) { - SessionRecapRoute( - openAndPopUp = openAndPopUp, - viewModel = hiltViewModel() - ) - } } } diff --git a/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt b/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt index 318fe7a..b020cc8 100644 --- a/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt +++ b/app/src/main/java/be/ugent/sel/studeez/activities/MainActivity.kt @@ -10,9 +10,15 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.lifecycleScope import be.ugent.sel.studeez.StudeezApp +import be.ugent.sel.studeez.screens.session.InvisibleSessionManager import be.ugent.sel.studeez.ui.theme.StudeezTheme import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +var onTimerInvisible: Job? = null @AndroidEntryPoint 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 diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt index 6c4169e..73ae1b5 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt @@ -27,11 +27,19 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import be.ugent.sel.studeez.common.ext.basicButton 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 @Composable 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 @@ -45,7 +53,7 @@ fun BasicButton( Button( onClick = onClick, modifier = modifier, - shape = RoundedCornerShape(20.dp), + shape = defaultButtonShape(), colors = colors, border = border, ) { diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/FloatingActionButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/FloatingActionButtonComposable.kt index 15005fa..bc40ead 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/FloatingActionButtonComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/FloatingActionButtonComposable.kt @@ -1,7 +1,9 @@ package be.ugent.sel.studeez.common.composable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.animation.core.animateFloat +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.Icon 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.DateRange 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.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.unit.dp +import be.ugent.sel.studeez.resources 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 -fun CollapsedAddButton() { - FloatingActionButton( - onClick = { /* TODO popup add options */ } +fun AddButton( + addButtonActions: AddButtonActions +) { + 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 -fun ExpandedAddButton() { - Row() { - IconButton(onClick = { /* TODO Go to next step */ }) { - Column (horizontalAlignment = Alignment.CenterHorizontally) { - Icon(imageVector = Icons.Default.Check, contentDescription = "Task") - Text(text = "Task") - } - } - IconButton(onClick = { /* TODO Go to next step */ }) { - Column (horizontalAlignment = Alignment.CenterHorizontally) { - Icon(imageVector = Icons.Default.Person, contentDescription = "Friend") - Text(text = "Friend") - } - } - IconButton(onClick = { /* TODO Go to next step */ }) { - Column (horizontalAlignment = Alignment.CenterHorizontally) { - Icon(imageVector = Icons.Default.DateRange, contentDescription = "Session") - Text(text = "Session") - } - } +fun ExpandedAddButton( + addButtonActions: AddButtonActions +) { + Row { + ExpandedEntry( + name = AppText.task, + imageVector = Icons.Default.Check, + onClick = addButtonActions.onTaskClick, + modifier = Modifier.padding(36.dp, HEIGHT_DIFFERENCE, 36.dp, 0.dp) + ) + + ExpandedEntry( + name = AppText.friend, + imageVector = Icons.Default.Person, + onClick = addButtonActions.onFriendClick + ) + + 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 @Composable -fun CollapsedAddButtonPreview() { - StudeezTheme { CollapsedAddButton() } +fun AddButtonPreview() { + StudeezTheme { AddButton( + addButtonActions = AddButtonActions({}, {}, {}) + )} } @Preview @Composable fun ExpandedAddButtonPreview() { - StudeezTheme { ExpandedAddButton() } + StudeezTheme { ExpandedAddButton ( + addButtonActions = AddButtonActions({}, {}, {}) + ) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/PrimaryScreenComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/PrimaryScreenComposable.kt index 79dec41..0b3ee6e 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/PrimaryScreenComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/PrimaryScreenComposable.kt @@ -57,7 +57,11 @@ fun PrimaryScreenTemplate( bottomBar = { NavigationBar(navigationBarActions) }, floatingActionButtonPosition = FabPosition.Center, isFloatingActionButtonDocked = true, - floatingActionButton = { CollapsedAddButton() } + floatingActionButton = { AddButton(AddButtonActions( + onTaskClick = navigationBarActions.onAddTaskClick, + onFriendClick = navigationBarActions.onAddFriendClick, + onSessionClick = navigationBarActions.onAddSessionClick + )) } ) { content(it) } @@ -70,7 +74,7 @@ fun PrimaryScreenPreview() { PrimaryScreenTemplate( "Preview screen", DrawerActions({}, {}, {}, {}, {}), - NavigationBarActions({ false }, {}, {}, {}, {}), + NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), { IconButton(onClick = { /*TODO*/ }) { Icon( diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt index 2c0b450..47dbb0b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt @@ -41,10 +41,12 @@ fun BasicField( fun LabelledInputField( value: String, onNewValue: (String) -> Unit, - @StringRes label: Int + @StringRes label: Int, + singleLine: Boolean = false ) { OutlinedTextField( value = value, + singleLine = singleLine, onValueChange = onNewValue, label = { Text(text = stringResource(id = label)) }, modifier = Modifier.fieldModifier() diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/TimePickerButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/TimePickerButtonComposable.kt new file mode 100644 index 0000000..c5e75cc --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/TimePickerButtonComposable.kt @@ -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 = 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) { + 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 = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerComposable.kt index 47571f5..2d4eab3 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerComposable.kt @@ -1,5 +1,6 @@ package be.ugent.sel.studeez.common.composable.drawer +import android.content.Context import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement 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.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -28,7 +30,7 @@ data class DrawerActions( val onTimersClick: () -> Unit, val onSettingsClick: () -> Unit, val onLogoutClick: () -> Unit, - val onAboutClick: () -> Unit, + val onAboutClick: (Context) -> Unit, ) fun getDrawerActions( @@ -41,7 +43,7 @@ fun getDrawerActions( onTimersClick = { drawerViewModel.onTimersClick(open) }, onSettingsClick = { drawerViewModel.onSettingsClick(open) }, 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( icon = Icons.Outlined.Info, text = resources().getString(R.string.about), - onClick = drawerActions.onAboutClick, + onClick = { drawerActions.onAboutClick(context) }, ) } } @@ -96,7 +99,7 @@ fun DrawerEntry( Row( horizontalArrangement = Arrangement.Center, modifier = Modifier - .clickable(onClick = { onClick() }) + .clickable(onClick = onClick) .fillMaxWidth() ) { Box( diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerViewModel.kt index d16d930..e55c342 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/drawer/DrawerViewModel.kt @@ -1,5 +1,10 @@ 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.LogService 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 javax.inject.Inject +const val REPO_URL: String = "https://github.ugent.be/SELab1/project2023-groep14/" + @HiltViewModel class DrawerViewModel @Inject constructor( private val accountDAO: AccountDAO, @@ -20,11 +27,11 @@ class DrawerViewModel @Inject constructor( } fun onTimersClick(openAndPopup: (String) -> Unit) { - openAndPopup(StudeezDestinations.TIMER_OVERVIEW_SCREEN) + openAndPopup(StudeezDestinations.TIMER_SCREEN) } fun onSettingsClick(open: (String) -> Unit) { - // TODO + open(StudeezDestinations.SETTINGS_SCREEN) } fun onLogoutClick(openAndPopUp: (String, String) -> Unit) { @@ -34,7 +41,8 @@ class DrawerViewModel @Inject constructor( } } - fun onAboutClick(open: (String) -> Unit) { - // TODO + fun onAboutClick(open: (String) -> Unit, context: Context) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(REPO_URL)) + context.startActivity(intent) } } diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt index a65216d..c4d6e33 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt @@ -1,6 +1,5 @@ package be.ugent.sel.studeez.common.composable.navbar -import android.util.Log import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigationItem import androidx.compose.material.Icon @@ -15,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp 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.SESSIONS_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.ui.theme.StudeezTheme @@ -22,10 +22,16 @@ import be.ugent.sel.studeez.R.string as AppText data class NavigationBarActions( val isSelectedTab: (String) -> Boolean, + val onHomeClick: () -> Unit, val onTasksClick: () -> Unit, val onSessionsClick: () -> Unit, val onProfileClick: () -> Unit, + + // AddButton + val onAddTaskClick: () -> Unit, + val onAddFriendClick: () -> Unit, + val onAddSessionClick: () -> Unit ) fun getNavigationBarActions( @@ -49,6 +55,16 @@ fun getNavigationBarActions( onProfileClick = { 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 - BottomNavigationItem(icon = {}, onClick = {}, selected = false) + BottomNavigationItem(icon = {}, onClick = {}, selected = false, enabled = false) BottomNavigationItem( icon = { @@ -87,8 +103,7 @@ fun NavigationBar( ) }, label = { Text(text = resources().getString(AppText.sessions)) }, - // TODO selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN), - selected = false, + selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN), onClick = navigationBarActions.onSessionsClick ) @@ -111,7 +126,7 @@ fun NavigationBar( fun NavigationBarPreview() { StudeezTheme { NavigationBar( - navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), + navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), ) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt index 8a39fc1..07a5bf9 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt @@ -1,13 +1,15 @@ 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.navigation.StudeezDestinations.HOME_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.TASKS_SCREEN import be.ugent.sel.studeez.screens.StudeezViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import be.ugent.sel.studeez.R.string as AppText @HiltViewModel class NavigationBarViewModel @Inject constructor( @@ -23,10 +25,25 @@ class NavigationBarViewModel @Inject constructor( } fun onSessionsClick(open: (String) -> Unit) { - // TODO + open(SESSIONS_SCREEN) } fun onProfileClick(open: (String) -> Unit) { 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 + } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/TimePickerComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/TimePickerComposable.kt new file mode 100644 index 0000000..3a59519 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/TimePickerComposable.kt @@ -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 + ) +} diff --git a/app/src/main/java/be/ugent/sel/studeez/common/ext/ShapeExt.kt b/app/src/main/java/be/ugent/sel/studeez/common/ext/ShapeExt.kt new file mode 100644 index 0000000..2114a74 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/ext/ShapeExt.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/EditTimerState.kt b/app/src/main/java/be/ugent/sel/studeez/data/EditTimerState.kt new file mode 100644 index 0000000..dceec8c --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/data/EditTimerState.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalCustomTimer.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalCustomTimer.kt index 1a2e099..7bc51f8 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalCustomTimer.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalCustomTimer.kt @@ -1,14 +1,10 @@ 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) { override fun tick() { if (!hasEnded()) { - time.minOne() + time-- totalStudyTime++ } } diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalEndlessTimer.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalEndlessTimer.kt index 41be874..51ee182 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalEndlessTimer.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalEndlessTimer.kt @@ -11,7 +11,7 @@ class FunctionalEndlessTimer : FunctionalTimer(0) { } override fun tick() { - time.plusOne() + time++ totalStudyTime++ } diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt index 40660e2..6d4f868 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt @@ -1,8 +1,5 @@ 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( private var studyTime: Int, private var breakTime: Int, repeats: Int @@ -25,7 +22,7 @@ class FunctionalPomodoroTimer( } isInBreak = !isInBreak } - time.minOne() + time-- if (!isInBreak) { totalStudyTime++ diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt index 760d656..1f4231a 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt @@ -1,11 +1,10 @@ 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.AbstractSessionScreen import com.google.firebase.Timestamp abstract class FunctionalTimer(initialValue: Int) { - val time: Time = Time(initialValue) + var time: Time = Time(initialValue) var totalStudyTime: Int = 0 fun getHoursMinutesSeconds(): HoursMinutesSeconds { diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt index 57b03a7..edccbd0 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt @@ -1,17 +1,21 @@ package be.ugent.sel.studeez.data.local.models.timer_functional -class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { - constructor(seconds: Int) : this( - hours = seconds / (60 * 60), - minutes = (seconds / 60) % 60, - seconds = seconds % 60, +data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { + + constructor(sec: Int): this( + hours = sec / (60 * 60), + minutes = (sec / (60)) % 60, + seconds = sec % 60, ) + fun getTotalSeconds(): Int { + return (hours * 60 * 60) + (minutes * 60) + seconds + } + override fun toString(): String { - return hours.toString().padStart(2, '0') + - ":" + - minutes.toString().padStart(2, '0') + - ":" + - seconds.toString().padStart(2, '0') + val hoursString = hours.toString().padStart(2, '0') + val minutesString = minutes.toString().padStart(2, '0') + val secondsString = seconds.toString().padStart(2, '0') + return "$hoursString:$minutesString:$secondsString" } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt index aad96e0..e37b374 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt @@ -1,16 +1,11 @@ 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() { - time-- - } - - fun plusOne() { - time++ - } + operator fun dec(): Time = Time(time - 1) fun getAsHMS(): HoursMinutesSeconds { return HoursMinutesSeconds(time) diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/CustomTimerInfo.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/CustomTimerInfo.kt index 5e06536..d88e39f 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/CustomTimerInfo.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/CustomTimerInfo.kt @@ -6,11 +6,10 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer class CustomTimerInfo( name: String, description: String, - private val studyTime: Int, + var studyTime: Int, id: String = "" ): TimerInfo(id, name, description) { - override fun getFunctionalTimer(): FunctionalTimer { return FunctionalCustomTimer(studyTime) } @@ -24,4 +23,8 @@ class CustomTimerInfo( ) } + override fun accept(visitor: TimerInfoVisitor): T { + return visitor.visitCustomTimerInfo(this) + } + } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/EndlessTimerInfo.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/EndlessTimerInfo.kt index d459a4e..45f7fd7 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/EndlessTimerInfo.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/EndlessTimerInfo.kt @@ -22,4 +22,8 @@ class EndlessTimerInfo( ) } + override fun accept(visitor: TimerInfoVisitor): T { + return visitor.visitEndlessTimerInfo(this) + } + } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt index 18bcea6..6dd6797 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt @@ -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.FunctionalTimer +import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor class PomodoroTimerInfo( name: String, description: String, - private val studyTime: Int, - private val breakTime: Int, - private val repeats: Int, + var studyTime: Int, + var breakTime: Int, + val repeats: Int, id: String = "" ): TimerInfo(id, name, description) { @@ -28,4 +29,8 @@ class PomodoroTimerInfo( ) } + override fun accept(visitor: TimerInfoVisitor): T { + return visitor.visitBreakTimerInfo(this) + } + } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/TimerInfo.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/TimerInfo.kt index 343e7e3..e4deded 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/TimerInfo.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/TimerInfo.kt @@ -7,8 +7,8 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer */ abstract class TimerInfo( val id: String, - val name: String, - val description: String + var name: String, + var description: String ) { /** @@ -21,6 +21,7 @@ abstract class TimerInfo( * TODO implementaties hebben nog hardgecodeerde strings. */ abstract fun asJson(): Map + abstract fun accept(visitor: TimerInfoVisitor): T } diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/TimerInfoVisitor.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/TimerInfoVisitor.kt new file mode 100644 index 0000000..e331c8d --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/TimerInfoVisitor.kt @@ -0,0 +1,11 @@ +package be.ugent.sel.studeez.data.local.models.timer_info + +interface TimerInfoVisitor { + + fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): T + + fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): T + + fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): T + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt index 5f4053a..966b4a1 100644 --- a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt +++ b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt @@ -1,29 +1,40 @@ package be.ugent.sel.studeez.navigation object StudeezDestinations { - const val SPLASH_SCREEN = "splash" - const val SIGN_UP_SCREEN = "signup" - const val LOGIN_SCREEN = "login" - + // NavBar 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_EDIT_SCREEN = "timer_edit" const val SESSION_SCREEN = "session" const val SESSION_RECAP = "session_recap" - const val SUBJECT_SCREEN = "subjects" const val ADD_SUBJECT_FORM = "add_subject" const val EDIT_SUBJECT_FORM = "edit_subject" const val TASKS_SCREEN = "tasks" const val ADD_TASK_FORM = "add_task" const val EDIT_TASK_FORM = "edit_task" - // const val SESSIONS_SCREEN = "sessions" - const val PROFILE_SCREEN = "profile" + // Friends flow + const val SEARCH_FRIENDS_SCREEN = "search_friends" -// const val TIMERS_SCREEN = "timers" -// const val SETTINGS_SCREEN = "settings" - - // Edit screens + // Create & edit screens + const val CREATE_TASK_SCREEN = "create_task" + const val CREATE_SESSION_SCREEN = "create_session" const val EDIT_PROFILE_SCREEN = "edit_profile" + + const val ADD_TIMER_SCREEN = "add_timer" } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt index 1f760e5..f02852e 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt @@ -33,13 +33,13 @@ fun HomeRoute( fun HomeScreen( onStartSessionClick: () -> Unit, drawerActions: DrawerActions, - navigationBarActions: NavigationBarActions, + navigationBarActions: NavigationBarActions ) { PrimaryScreenTemplate( title = resources().getString(R.string.home), drawerActions = drawerActions, navigationBarActions = navigationBarActions, - barAction = { FriendsAction() } + // TODO barAction = { FriendsAction() } ) { BasicButton(R.string.start_session, Modifier.basicButton()) { onStartSessionClick() @@ -63,6 +63,6 @@ fun HomeScreenPreview() { HomeScreen( onStartSessionClick = {}, drawerActions = DrawerActions({}, {}, {}, {}, {}), - navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}) + navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileEditScreen.kt index 116a5fb..c6fcbaf 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileEditScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileEditScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import androidx.hilt.navigation.compose.hiltViewModel import be.ugent.sel.studeez.R import be.ugent.sel.studeez.common.composable.BasicTextButton import be.ugent.sel.studeez.common.composable.LabelledInputField @@ -64,7 +63,10 @@ fun EditProfileScreen( BasicTextButton( text = R.string.save, Modifier.textButton(), - action = editProfileActions.onSaveClick + action = { + editProfileActions.onSaveClick() + goBack() + } ) BasicTextButton( text = R.string.delete_profile, diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileScreen.kt index 0b4a67f..9c76337 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/profile/ProfileScreen.kt @@ -88,6 +88,6 @@ fun ProfileScreenPreview() { ProfileScreen( profileActions = ProfileActions({ null }, {}), drawerActions = DrawerActions({}, {}, {}, {}, {}), - navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}) + navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/InvisibleSessionManager.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/InvisibleSessionManager.kt new file mode 100644 index 0000000..9051fa8 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/InvisibleSessionManager.kt @@ -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() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionRoute.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionRoute.kt index a503067..084ff43 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionRoute.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionRoute.kt @@ -12,7 +12,7 @@ import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen data class SessionActions( val getTimer: () -> FunctionalTimer, val getTask: () -> String, - val prepareMediaPlayer: () -> Unit, + val startMediaPlayer: () -> Unit, val releaseMediaPlayer: () -> Unit, val endSession: () -> Unit ) @@ -26,8 +26,8 @@ private fun getSessionActions( getTimer = viewModel::getTimer, getTask = viewModel::getTask, endSession = { viewModel.endSession(openAndPopUp) }, - prepareMediaPlayer = mediaplayer::prepareAsync, - releaseMediaPlayer = mediaplayer::release + startMediaPlayer = mediaplayer::start, + releaseMediaPlayer = mediaplayer::release, ) } @@ -39,26 +39,15 @@ fun SessionRoute( ) { val context = LocalContext.current val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) - val mediaplayer = MediaPlayer() - mediaplayer.setDataSource(context, uri) - mediaplayer.setOnCompletionListener { - mediaplayer.stop() - //if (timerEnd) { -// mediaplayer.release() - //} - } - mediaplayer.setOnPreparedListener { -// mediaplayer.start() - } + val mediaplayer = MediaPlayer.create(context, uri) + mediaplayer.isLooping = false - val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen()) + InvisibleSessionManager.setParameters( + viewModel = viewModel, + mediaplayer = mediaplayer + ) - //val sessionScreen = when (val timer = viewModel.getTimer()) { - // is FunctionalCustomTimer -> CustomSessionScreen(timer) - // is FunctionalPomodoroTimer -> BreakSessionScreen(timer) - // is FunctionalEndlessTimer -> EndlessSessionScreen() - // else -> throw java.lang.IllegalArgumentException("Unknown Timer") - //} + val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer)) sessionScreen( open = open, diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt index c3a4cd8..65f5d24 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt @@ -9,7 +9,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text 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.Modifier 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.sp 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 kotlinx.coroutines.delay import kotlin.time.Duration.Companion.seconds abstract class AbstractSessionScreen { - var timerEnd = false - @Composable operator fun invoke( open: (String) -> Unit, @@ -75,22 +76,10 @@ abstract class AbstractSessionScreen { LaunchedEffect(tikker) { delay(1.seconds) sessionActions.getTimer().tick() + callMediaPlayer() 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() Column { Text( @@ -137,6 +126,8 @@ abstract class AbstractSessionScreen { @Composable abstract fun motivationString(): String + abstract fun callMediaPlayer() + } @Preview @@ -145,6 +136,7 @@ fun TimerPreview() { val sessionScreen = object : AbstractSessionScreen() { @Composable override fun motivationString(): String = "Test" + override fun callMediaPlayer() {} } sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {})) diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt index edc8475..8fa45ff 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt @@ -1,5 +1,6 @@ package be.ugent.sel.studeez.screens.session.sessionScreens +import android.media.MediaPlayer import androidx.compose.runtime.Composable import be.ugent.sel.studeez.R 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 class BreakSessionScreen( - private val funPomoDoroTimer: FunctionalPomodoroTimer + private val funPomoDoroTimer: FunctionalPomodoroTimer, + private var mediaplayer: MediaPlayer? ): AbstractSessionScreen() { @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() + } + } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/CustomSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/CustomSessionScreen.kt index 36ee492..7fc60bc 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/CustomSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/CustomSessionScreen.kt @@ -1,5 +1,6 @@ package be.ugent.sel.studeez.screens.session.sessionScreens +import android.media.MediaPlayer import androidx.compose.runtime.Composable import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer import be.ugent.sel.studeez.resources @@ -7,7 +8,8 @@ import be.ugent.sel.studeez.R.string as AppText class CustomSessionScreen( - private val functionalTimer: FunctionalCustomTimer + private val functionalTimer: FunctionalCustomTimer, + private var mediaplayer: MediaPlayer? ): AbstractSessionScreen() { @Composable @@ -18,4 +20,16 @@ class CustomSessionScreen( return resources().getString(AppText.state_focus) } + override fun callMediaPlayer() { + if (functionalTimer.hasEnded()) { + mediaplayer?.let { it: MediaPlayer -> + it.setOnCompletionListener { + it.release() + mediaplayer = null + } + it.start() + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/EndlessSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/EndlessSessionScreen.kt index fc46c9d..be67cff 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/EndlessSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/EndlessSessionScreen.kt @@ -11,4 +11,6 @@ class EndlessSessionScreen : AbstractSessionScreen() { override fun motivationString(): String { return resources().getString(AppText.state_focus) } + + override fun callMediaPlayer() {} } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/GetSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/GetSessionScreen.kt index e378661..98b2d5e 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/GetSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/GetSessionScreen.kt @@ -1,17 +1,18 @@ 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.FunctionalEndlessTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor -class GetSessionScreen : FunctionalTimerVisitor { +class GetSessionScreen(private val mediaplayer: MediaPlayer?) : FunctionalTimerVisitor { override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen = - CustomSessionScreen(functionalCustomTimer) + CustomSessionScreen(functionalCustomTimer, mediaplayer) override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen = EndlessSessionScreen() override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen = - BreakSessionScreen(functionalPomodoroTimer) + BreakSessionScreen(functionalPomodoroTimer, mediaplayer) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt index 63878f7..2d06e0b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt @@ -26,7 +26,7 @@ fun getSessionRecapActions( return SessionRecapActions( viewModel::getSessionReport, {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 studyTime: Int = sessionReport.studyTime val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() - Column { - Text(text = "You studied: ${hms.hours} : ${hms.minutes} : ${hms.seconds}") + Column( + modifier = modifier + ) { + Text(text = "You studied: $hms") BasicButton( R.string.save, Modifier.basicButton() diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt index e9ad6b1..5fb4943 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt @@ -11,7 +11,7 @@ import javax.inject.Inject @HiltViewModel class SessionRecapViewModel @Inject constructor( - private val sessionReportState: SessionReportState, + sessionReportState: SessionReportState, private val sessionDAO: SessionDAO, logService: LogService ) : StudeezViewModel(logService) { diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/sessions/SessionsScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/sessions/SessionsScreen.kt new file mode 100644 index 0000000..fe60ca8 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/sessions/SessionsScreen.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/settings/SettingsScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/settings/SettingsScreen.kt new file mode 100644 index 0000000..e1098b7 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/settings/SettingsScreen.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt index 1813460..15a3925 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt @@ -72,7 +72,7 @@ fun SubjectScreen( fun SubjectScreenPreview() { SubjectScreen( drawerActions = DrawerActions({}, {}, {}, {}, {}), - navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), + navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), addSubject = {}, getSubjects = { flowOf() }, onViewSubject = {}, diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddScreen.kt new file mode 100644 index 0000000..7cb6a8f --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddScreen.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddViewModel.kt new file mode 100644 index 0000000..9ab766c --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddViewModel.kt @@ -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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectScreen.kt new file mode 100644 index 0000000..d14bb86 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectScreen.kt @@ -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 = 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) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectViewModel.kt new file mode 100644 index 0000000..1892833 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectViewModel.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/GetTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/GetTimerEditScreen.kt new file mode 100644 index 0000000..b22b775 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/GetTimerEditScreen.kt @@ -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 { + + 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) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditScreen.kt new file mode 100644 index 0000000..649caf6 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditScreen.kt @@ -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) + } + } +} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditViewModel.kt new file mode 100644 index 0000000..3258f24 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditViewModel.kt @@ -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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/AbstractTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/AbstractTimerEditScreen.kt new file mode 100644 index 0000000..420d734 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/AbstractTimerEditScreen.kt @@ -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. + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/BreakTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/BreakTimerEditScreen.kt new file mode 100644 index 0000000..b6104b6 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/BreakTimerEditScreen.kt @@ -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 = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/CustomTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/CustomTimerEditScreen.kt new file mode 100644 index 0000000..f3278d5 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/CustomTimerEditScreen.kt @@ -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 = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/EndlessTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/EndlessTimerEditScreen.kt new file mode 100644 index 0000000..0e26209 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/EndlessTimerEditScreen.kt @@ -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 = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt index 9489a30..dd3f062 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt @@ -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.data.local.models.timer_info.CustomTimerInfo 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 kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @@ -24,15 +25,18 @@ data class TimerOverviewActions( val getUserTimers: () -> Flow>, val getDefaultTimers: () -> List, val onEditClick: (TimerInfo) -> Unit, + val onAddClick: () -> Unit, ) fun getTimerOverviewActions( viewModel: TimerOverviewViewModel, + open: (String) -> Unit, ): TimerOverviewActions { return TimerOverviewActions( getUserTimers = viewModel::getUserTimers, getDefaultTimers = viewModel::getDefaultTimers, - onEditClick = { viewModel.update(it) }, + onEditClick = { viewModel.update(it, open) }, + onAddClick = { viewModel.create(open) } ) } @@ -40,10 +44,11 @@ fun getTimerOverviewActions( fun TimerOverviewRoute( viewModel: TimerOverviewViewModel, drawerActions: DrawerActions, + open: (String) -> Unit ) { TimerOverviewScreen( - timerOverviewActions = getTimerOverviewActions(viewModel), - drawerActions = drawerActions, + timerOverviewActions = getTimerOverviewActions(viewModel, open), + drawerActions = drawerActions ) } @@ -59,8 +64,16 @@ fun TimerOverviewScreen( title = resources().getString(R.string.timers), drawerActions = drawerActions ) { - Column { + Column { // TODO knop beneden 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 items(timerOverviewActions.getDefaultTimers()) { TimerEntry(timerInfo = it) {} @@ -77,9 +90,13 @@ fun TimerOverviewScreen( } } - } - BasicButton(R.string.add_timer, Modifier.basicButton()) { - // TODO + + // TODO uit lazy column + item { + BasicButton(R.string.add_timer, Modifier.basicButton()) { + timerOverviewActions.onAddClick() + } + } } } } @@ -95,7 +112,9 @@ fun TimerOverviewPreview() { timerOverviewActions = TimerOverviewActions( { flowOf() }, { listOf(customTimer, customTimer) }, - {}), + {}, + {} + ), drawerActions = DrawerActions({}, {}, {}, {}, {}) ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt index c2be1e9..77a5a6e 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt @@ -1,9 +1,11 @@ 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.domain.ConfigurationService 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 @@ -13,6 +15,7 @@ import javax.inject.Inject class TimerOverviewViewModel @Inject constructor( private val configurationService: ConfigurationService, private val timerDAO: TimerDAO, + private val editTimerState: EditTimerState, logService: LogService ) : StudeezViewModel(logService) { @@ -24,7 +27,14 @@ class TimerOverviewViewModel @Inject constructor( 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) diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt new file mode 100644 index 0000000..fcfa79a --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt @@ -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", +) \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt new file mode 100644 index 0000000..d507974 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt @@ -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) + } +} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt new file mode 100644 index 0000000..dc7bbda --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt @@ -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() + ) + } +} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt index ac46b5c..2f17e65 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt @@ -8,7 +8,11 @@ import androidx.compose.ui.tooling.preview.Preview import be.ugent.sel.studeez.R import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate 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.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.resources import kotlinx.coroutines.flow.Flow @@ -17,6 +21,7 @@ import kotlinx.coroutines.flow.flowOf data class TimerSelectionActions( val getAllTimers: () -> Flow>, val startSession: (TimerInfo) -> Unit, + val customTimeStudyTime: Int ) fun getTimerSelectionActions( @@ -26,6 +31,7 @@ fun getTimerSelectionActions( return TimerSelectionActions( getAllTimers = viewModel::getAllTimers, startSession = { viewModel.startSession(open, it) }, + customTimeStudyTime = viewModel.customTimerStudyTime.value ) } @@ -52,6 +58,11 @@ fun TimerSelectionScreen( popUp = popUp ) { LazyColumn { + // Custom timer with duration selection button + item { + CustomTimerEntry(timerSelectionActions) + } + // All timers items(timers.value) { timerInfo -> 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 @Composable fun TimerSelectionPreview() { TimerSelectionScreen( - timerSelectionActions = TimerSelectionActions({ flowOf() }, {}), + timerSelectionActions = TimerSelectionActions({ flowOf() }, {}, 0), popUp = {} ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt index c555bfa..ab42973 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt @@ -1,5 +1,9 @@ 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.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.domain.LogService @@ -17,6 +21,8 @@ class TimerSelectionViewModel @Inject constructor( logService: LogService ) : StudeezViewModel(logService) { + var customTimerStudyTime: MutableState = mutableStateOf(0) + fun getAllTimers() : Flow> { return timerDAO.getAllTimers() } diff --git a/app/src/main/java/be/ugent/sel/studeez/ui/theme/Theme.kt b/app/src/main/java/be/ugent/sel/studeez/ui/theme/Theme.kt index bc2c315..9a29e85 100644 --- a/app/src/main/java/be/ugent/sel/studeez/ui/theme/Theme.kt +++ b/app/src/main/java/be/ugent/sel/studeez/ui/theme/Theme.kt @@ -12,7 +12,9 @@ import androidx.compose.ui.graphics.Color private val DarkColorPalette = darkColors( primary = Blue100, primaryVariant = Blue120, - secondary = Yellow100 + secondary = Yellow100, + + onPrimary = Color.White ) private val LightColorPalette = lightColors( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a069c87..0ec0e9d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ - + + Studeez Username Email @@ -7,20 +8,83 @@ Repeat password Menu - - Confirm - Save - Discard - Cancel - Go back - Next - Start + + Confirm + Save + Discard + Cancel + Go back + Next + Start - - Success! - Try again - Something wrong happened. Please try again. - Please insert a valid email. + + Success! + Try again + Something wrong happened. Please try again. + Please insert a valid email. + + + + + Home + Start session + + + Tasks + Task + My Subjects + New Subject + New Task + Edit Subject + Edit Task + Delete Subject + Delete Task + View + + + 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. + Sessions + Session + End session + Upcoming sessions + + + Profile + Unknown username + Edit profile + Editing profile + Delete profile + + + + Log out + Profile Picture + Normal user + + + Timers + Edit + Add timer + Select time + Focus! + + Focus one more time! + Focus! (%d break remaining) + Focus! (%d breaks remaining) + + Done! + Take a break! + Custom + Select how long you want to study + + + 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. + Settings + + + About Studeez + + Create account @@ -36,58 +100,42 @@ Check your inbox for the recovery email. Password cannot be empty. - - Home - Start session + - - Tasks - My Subjects - New Subject - New Task - Edit Subject - Edit Task - Delete Subject - Delete Task - View + - - Name - - - Sessions - - - Profile - Unknown username - Edit profile - Editing profile - Delete profile - - Friends + Friend + Adding friends still needs to be implemented. Hang on tight! - - Log out - Profile Picture - Normal user + - - Timers - Edit - Add timer - Focus! - - Focus one more time! - Focus! (%d break remaining) - Focus! (%d breaks remaining) - - Done! - Take a break! + + Creating tasks still needs to be implemented. Hang on tight! - - Settings + + Creating sessions still needs to be implemented. Hang on tight! + + + Timer description cannot be empty! + Timer description + Timer name cannot be empty! + Timer name + Open Time Picker + " hours and " + " minutes of breaktime" + " break" + " breaks" + With breaks? + " hours and " + " minutes of studytime" + How long do you want to study? + + + Name + Description + Study Time + Break Time + Number of Repeats - - About Studeez diff --git a/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalCustomTimerUnitTest.kt b/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalCustomTimerUnitTest.kt index 6e51b4e..548fe9d 100644 --- a/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalCustomTimerUnitTest.kt +++ b/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalCustomTimerUnitTest.kt @@ -1,7 +1,6 @@ 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.FunctionalTimer import org.junit.Assert import org.junit.Test diff --git a/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalEndlessTimerUnitTest.kt b/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalEndlessTimerUnitTest.kt index b99c901..17733bc 100644 --- a/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalEndlessTimerUnitTest.kt +++ b/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalEndlessTimerUnitTest.kt @@ -1,7 +1,6 @@ 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.FunctionalTimer import org.junit.Assert import org.junit.Test diff --git a/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalPomodoroTimerUnitTest.kt b/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalPomodoroTimerUnitTest.kt index 3ff1499..4b259c8 100644 --- a/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalPomodoroTimerUnitTest.kt +++ b/app/src/test/java/be/ugent/sel/studeez/timer_functional/FunctionalPomodoroTimerUnitTest.kt @@ -1,7 +1,6 @@ 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.FunctionalTimer import org.junit.Assert import org.junit.Test diff --git a/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt b/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt new file mode 100644 index 0000000..f9e34c3 --- /dev/null +++ b/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt @@ -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 + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3c5031e..edf11ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,8 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. 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. # 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