Resolved merge conflicts (call with friends)

This commit is contained in:
lbarraga 2023-05-04 00:12:04 +02:00
commit 46109ac4ff
31 changed files with 1011 additions and 205 deletions

2
.idea/misc.xml generated
View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View file

@ -2,18 +2,8 @@ package be.ugent.sel.studeez
import android.content.res.Resources import android.content.res.Resources
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme import androidx.compose.material.*
import androidx.compose.material.Scaffold import androidx.compose.runtime.*
import androidx.compose.material.ScaffoldState
import androidx.compose.material.Snackbar
import androidx.compose.material.SnackbarHost
import androidx.compose.material.Surface
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -38,10 +28,13 @@ import be.ugent.sel.studeez.screens.profile.EditProfileRoute
import be.ugent.sel.studeez.screens.profile.ProfileRoute import be.ugent.sel.studeez.screens.profile.ProfileRoute
import be.ugent.sel.studeez.screens.session.SessionRoute import be.ugent.sel.studeez.screens.session.SessionRoute
import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute
import be.ugent.sel.studeez.screens.sessions.SessionsRoute
import be.ugent.sel.studeez.screens.settings.SettingsRoute
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
import be.ugent.sel.studeez.screens.splash.SplashRoute import be.ugent.sel.studeez.screens.splash.SplashRoute
import be.ugent.sel.studeez.screens.timer_edit.TimerEditRoute import be.ugent.sel.studeez.screens.timer_edit.TimerEditRoute
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute
import be.ugent.sel.studeez.screens.timer_overview.add_timer.AddTimerRoute
import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -92,7 +85,7 @@ fun resources(): Resources {
@Composable @Composable
fun StudeezNavGraph( fun StudeezNavGraph(
appState: StudeezAppstate, appState: StudeezAppstate,
modifier: Modifier, modifier: Modifier = Modifier,
) { ) {
val drawerViewModel: DrawerViewModel = hiltViewModel() val drawerViewModel: DrawerViewModel = hiltViewModel()
val navBarViewModel: NavigationBarViewModel = hiltViewModel() val navBarViewModel: NavigationBarViewModel = hiltViewModel()
@ -114,8 +107,52 @@ fun StudeezNavGraph(
startDestination = StudeezDestinations.SPLASH_SCREEN, startDestination = StudeezDestinations.SPLASH_SCREEN,
modifier = modifier, modifier = modifier,
) { ) {
// NavBar
composable(StudeezDestinations.HOME_SCREEN) {
HomeRoute(
open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
composable(StudeezDestinations.TASKS_SCREEN) {
// TODO
}
composable(StudeezDestinations.SESSIONS_SCREEN) {
SessionsRoute(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
composable(StudeezDestinations.PROFILE_SCREEN) {
ProfileRoute(
open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
// 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) { composable(StudeezDestinations.SPLASH_SCREEN) {
SplashRoute( SplashRoute(
openAndPopUp, openAndPopUp,
@ -137,40 +174,12 @@ fun StudeezNavGraph(
) )
} }
composable(StudeezDestinations.HOME_SCREEN) { // Studying flow
HomeRoute( composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open, open,
goBack,
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
)
}
// TODO Tasks screen
// TODO Sessions screen
composable(StudeezDestinations.PROFILE_SCREEN) {
ProfileRoute(
open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
)
}
composable(StudeezDestinations.TIMER_OVERVIEW_SCREEN) {
TimerOverviewRoute(
viewModel = hiltViewModel(),
drawerActions = drawerActions,
open = open
)
}
composable(StudeezDestinations.TIMER_EDIT_SCREEN) {
TimerEditRoute(
open = open,
popUp = goBack,
viewModel = hiltViewModel()
) )
} }
@ -182,10 +191,43 @@ fun StudeezNavGraph(
) )
} }
// TODO Timers screen composable(StudeezDestinations.SESSION_RECAP) {
// TODO Settings screen SessionRecapRoute(
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.ADD_TIMER_SCREEN) {
AddTimerRoute(
open = open,
goBack = goBack,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.TIMER_EDIT_SCREEN) {
TimerEditRoute(
open = open,
popUp = goBack,
viewModel = hiltViewModel()
)
}
// Friends flow
composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) {
// TODO
}
// Create & edit screens
composable(StudeezDestinations.CREATE_TASK_SCREEN) {
// TODO
}
composable(StudeezDestinations.CREATE_SESSION_SCREEN) {
// TODO
}
// Edit screens
composable(StudeezDestinations.EDIT_PROFILE_SCREEN) { composable(StudeezDestinations.EDIT_PROFILE_SCREEN) {
EditProfileRoute( EditProfileRoute(
goBack, goBack,
@ -193,20 +235,5 @@ fun StudeezNavGraph(
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
) )
} }
composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open,
goBack,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SESSION_RECAP) {
SessionRecapRoute(
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel()
)
}
} }
} }

View file

@ -2,7 +2,6 @@ package be.ugent.sel.studeez.common.composable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -13,10 +12,18 @@ import androidx.compose.ui.unit.sp
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.card import be.ugent.sel.studeez.common.ext.card
import be.ugent.sel.studeez.common.ext.defaultButtonShape
@Composable @Composable
fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) {
TextButton(onClick = action, modifier = modifier) { Text(text = stringResource(text)) } TextButton(
onClick = action,
modifier = modifier
) {
Text(
text = stringResource(text)
)
}
} }
@Composable @Composable
@ -30,7 +37,7 @@ fun BasicButton(
Button( Button(
onClick = onClick, onClick = onClick,
modifier = modifier, modifier = modifier,
shape = RoundedCornerShape(20.dp), shape = defaultButtonShape(),
colors = colors, colors = colors,
border = border, border = border,
) { ) {
@ -47,6 +54,25 @@ fun BasicButtonPreview() {
BasicButton(text = R.string.add_timer, modifier = Modifier.basicButton()) {} BasicButton(text = R.string.add_timer, modifier = Modifier.basicButton()) {}
} }
@Composable
fun NotInternationalisedButton(
text: String,
modifier: Modifier = Modifier,
colors: ButtonColors = ButtonDefaults.buttonColors(),
border: BorderStroke? = null,
onClick: () -> Unit
) {
Button(
onClick = onClick,
modifier = modifier,
shape = defaultButtonShape(),
colors = colors,
border = border
) {
Text(text = text)
}
}
@Composable @Composable
fun StealthButton( fun StealthButton(
@StringRes text: Int, @StringRes text: Int,

View file

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

View file

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

View file

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

View file

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

View file

@ -14,16 +14,24 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.TASKS_SCREEN
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
data class NavigationBarActions( data class NavigationBarActions(
val isSelectedTab: (String) -> Boolean, val isSelectedTab: (String) -> Boolean,
val onHomeClick: () -> Unit, val onHomeClick: () -> Unit,
val onTasksClick: () -> Unit, val onTasksClick: () -> Unit,
val onSessionsClick: () -> Unit, val onSessionsClick: () -> Unit,
val onProfileClick: () -> Unit, val onProfileClick: () -> Unit,
// AddButton
val onAddTaskClick: () -> Unit,
val onAddFriendClick: () -> Unit,
val onAddSessionClick: () -> Unit
) )
fun getNavigationBarActions( fun getNavigationBarActions(
@ -35,6 +43,7 @@ fun getNavigationBarActions(
isSelectedTab = { screen -> isSelectedTab = { screen ->
screen == getCurrentScreen() screen == getCurrentScreen()
}, },
onHomeClick = { onHomeClick = {
navigationBarViewModel.onHomeClick(open) navigationBarViewModel.onHomeClick(open)
}, },
@ -47,6 +56,16 @@ fun getNavigationBarActions(
onProfileClick = { onProfileClick = {
navigationBarViewModel.onProfileClick(open) navigationBarViewModel.onProfileClick(open)
}, },
onAddTaskClick = {
navigationBarViewModel.onAddTaskClick(open)
},
onAddFriendClick = {
navigationBarViewModel.onAddFriendClick(open)
},
onAddSessionClick = {
navigationBarViewModel.onAddSessionClick(open)
}
) )
} }
@ -71,13 +90,12 @@ fun NavigationBar(
) )
}, },
label = { Text(text = resources().getString(AppText.tasks)) }, label = { Text(text = resources().getString(AppText.tasks)) },
// TODO selected = navigationBarActions.isSelectedTab(TASKS_SCREEN), selected = navigationBarActions.isSelectedTab(TASKS_SCREEN),
selected = false,
onClick = navigationBarActions.onTasksClick onClick = navigationBarActions.onTasksClick
) )
// Hack to space the entries in the navigation bar, make space for fab // Hack to space the entries in the navigation bar, make space for fab
BottomNavigationItem(icon = {}, onClick = {}, selected = false) BottomNavigationItem(icon = {}, onClick = {}, selected = false, enabled = false)
BottomNavigationItem( BottomNavigationItem(
icon = { icon = {
@ -86,8 +104,7 @@ fun NavigationBar(
) )
}, },
label = { Text(text = resources().getString(AppText.sessions)) }, label = { Text(text = resources().getString(AppText.sessions)) },
// TODO selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN), selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN),
selected = false,
onClick = navigationBarActions.onSessionsClick onClick = navigationBarActions.onSessionsClick
) )
@ -110,7 +127,7 @@ fun NavigationBar(
fun NavigationBarPreview() { fun NavigationBarPreview() {
StudeezTheme { StudeezTheme {
NavigationBar( NavigationBar(
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
) )
} }
} }

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) {
override fun tick() { override fun tick() {
if (!hasEnded()) { if (!hasEnded()) {
time++ time--
totalStudyTime++ totalStudyTime++
} }
} }

View file

@ -1,23 +1,34 @@
package be.ugent.sel.studeez.navigation package be.ugent.sel.studeez.navigation
object StudeezDestinations { object StudeezDestinations {
const val SPLASH_SCREEN = "splash" // NavBar
const val SIGN_UP_SCREEN = "signup"
const val LOGIN_SCREEN = "login"
const val HOME_SCREEN = "home" const val HOME_SCREEN = "home"
const val TIMER_OVERVIEW_SCREEN = "timer_overview" const val TASKS_SCREEN = "tasks"
const val SESSIONS_SCREEN = "sessions"
const val PROFILE_SCREEN = "profile"
// Drawer
const val TIMER_SCREEN = "timer_overview"
const val SETTINGS_SCREEN = "settings"
// Login flow
const val SPLASH_SCREEN = "splash"
const val LOGIN_SCREEN = "login"
const val SIGN_UP_SCREEN = "signup"
// Studying flow
const val TIMER_SELECTION_SCREEN = "timer_selection" const val TIMER_SELECTION_SCREEN = "timer_selection"
const val TIMER_EDIT_SCREEN = "timer_edit" const val TIMER_EDIT_SCREEN = "timer_edit"
const val SESSION_SCREEN = "session" const val SESSION_SCREEN = "session"
const val SESSION_RECAP = "session_recap" const val SESSION_RECAP = "session_recap"
// const val TASKS_SCREEN = "tasks"
// const val SESSIONS_SCREEN = "sessions"
const val PROFILE_SCREEN = "profile"
// const val TIMERS_SCREEN = "timers" // Friends flow
// const val SETTINGS_SCREEN = "settings" const val SEARCH_FRIENDS_SCREEN = "search_friends"
// 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 EDIT_PROFILE_SCREEN = "edit_profile"
const val ADD_TIMER_SCREEN = "add_timer"
} }

View file

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

View file

@ -63,7 +63,10 @@ fun EditProfileScreen(
BasicTextButton( BasicTextButton(
text = R.string.save, text = R.string.save,
Modifier.textButton(), Modifier.textButton(),
action = editProfileActions.onSaveClick action = {
editProfileActions.onSaveClick()
goBack()
}
) )
BasicTextButton( BasicTextButton(
text = R.string.delete_profile, text = R.string.delete_profile,

View file

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

View file

@ -47,8 +47,11 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi
val sessionReport: SessionReport = sessionRecapActions.getSessionReport() val sessionReport: SessionReport = sessionRecapActions.getSessionReport()
val studyTime: Int = sessionReport.studyTime val studyTime: Int = sessionReport.studyTime
val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS()
Column { Column(
modifier = modifier
) {
Text(text = "You studied: $hms") Text(text = "You studied: $hms")
BasicButton( BasicButton(
R.string.save, Modifier.basicButton() R.string.save, Modifier.basicButton()
) { ) {

View file

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

View file

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

View file

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

View file

@ -42,6 +42,8 @@ abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) {
onNewValue = { name = it }, onNewValue = { name = it },
label = R.string.name label = R.string.name
) )
repeat(20) {
LabelledInputField( LabelledInputField(
value = description, value = description,
onNewValue = { description = it }, onNewValue = { description = it },
@ -49,6 +51,8 @@ abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) {
singleLine = false singleLine = false
) )
}
ExtraFields() ExtraFields()
} }

View file

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

View file

@ -32,6 +32,10 @@ class TimerOverviewViewModel @Inject constructor(
open(StudeezDestinations.TIMER_EDIT_SCREEN) open(StudeezDestinations.TIMER_EDIT_SCREEN)
} }
fun create(open: (String) -> Unit) {
open(StudeezDestinations.ADD_TIMER_SCREEN)
}
fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo) fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo)
fun save(timerInfo: TimerInfo) = timerDAO.saveTimer(timerInfo) fun save(timerInfo: TimerInfo) = timerDAO.saveTimer(timerInfo)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,6 @@
<resources> <resources>
<!-- Common --> <!-- ========== Common ========== -->
<string name="app_name">Studeez</string> <string name="app_name">Studeez</string>
<string name="username">Username</string> <string name="username">Username</string>
<string name="email">Email</string> <string name="email">Email</string>
@ -22,6 +23,61 @@
<string name="generic_error">Something wrong happened. Please try again.</string> <string name="generic_error">Something wrong happened. Please try again.</string>
<string name="email_error">Please insert a valid email.</string> <string name="email_error">Please insert a valid email.</string>
<!-- ========== NavBar ========== -->
<!-- HomeScreen -->
<string name="home">Home</string>
<string name="start_session">Start session</string>
<!-- Tasks -->
<string name="tasks">Tasks</string>
<string name="task">Task</string>
<!-- Sessions -->
<string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. -->
<string name="sessions">Sessions</string>
<string name="session">Session</string>
<string name="end_session">End session</string>
<string name="upcoming_sessions">Upcoming sessions</string>
<!-- Profile -->
<string name="profile">Profile</string>
<string name="no_username">Unknown username</string>
<string name="edit_profile">Edit profile</string>
<string name="editing_profile">Editing profile</string>
<string name="delete_profile">Delete profile</string>
<!-- ========== Drawer ========== -->
<string name="log_out">Log out</string>
<string name="profile_picture_description">Profile Picture</string>
<string name="user_description">Normal user</string>
<!-- Timers -->
<string name="timers">Timers</string>
<string name="edit">Edit</string>
<string name="add_timer">Add timer</string>
<string name="pick_time">Select time</string>
<string name="state_focus">Focus!</string>
<plurals name="state_focus_remaining">
<item quantity="zero">Focus one more time!</item>
<item quantity="one">Focus! (%d break remaining)</item>
<item quantity="other">Focus! (%d breaks remaining)</item>
</plurals>
<string name="state_done">Done!</string>
<string name="state_take_a_break">Take a break!</string>
<string name="custom_name">Custom</string>
<string name="custom_description">Select how long you want to study</string>
<!-- Settings -->
<string name="settings_temp_description">Looks like you found the settings screen! In the future, this will enable you to edit your preferenes such as light/dark mode, end sessions automatically when we detect you are gone etc.</string> <!-- TODO Remove this description line once implemented. -->
<string name="settings">Settings</string>
<!-- About -->
<string name="about">About Studeez</string>
<!-- ========== Login flow ========== -->
<!-- SignUpScreen --> <!-- SignUpScreen -->
<string name="create_account">Create account</string> <string name="create_account">Create account</string>
<string name="password_error">Your password should have at least six characters and include one digit, one lower case letter and one upper case letter.</string> <string name="password_error">Your password should have at least six characters and include one digit, one lower case letter and one upper case letter.</string>
@ -36,44 +92,36 @@
<string name="recovery_email_sent">Check your inbox for the recovery email.</string> <string name="recovery_email_sent">Check your inbox for the recovery email.</string>
<string name="empty_password_error">Password cannot be empty.</string> <string name="empty_password_error">Password cannot be empty.</string>
<!-- HomeScreen --> <!-- ========== Studying flow ========== -->
<string name="home">Home</string>
<string name="start_session">Start session</string>
<!-- Tasks --> <!-- ========== Friends flow ========== -->
<string name="tasks">Tasks</string>
<!-- Sessions -->
<string name="sessions">Sessions</string>
<string name="end_session">End session</string>
<!-- Profile -->
<string name="profile">Profile</string>
<string name="no_username">Unknown username</string>
<string name="edit_profile">Edit profile</string>
<string name="editing_profile">Editing profile</string>
<string name="delete_profile">Delete profile</string>
<!-- Friends -->
<string name="friends">Friends</string> <string name="friends">Friends</string>
<string name="friend">Friend</string>
<string name="add_friend_not_possible_yet">Adding friends still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. -->
<!-- Drawer / SideMenu --> <!-- ========== Create & edit screens ========== -->
<string name="log_out">Log out</string>
<string name="profile_picture_description">Profile Picture</string>
<string name="user_description">Normal user</string>
<!-- Timers --> <!-- Task -->
<string name="timers">Timers</string> <string name="create_task_not_possible_yet">Creating tasks still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. -->
<string name="edit">Edit</string>
<string name="add_timer">Add timer</string> <!-- Session -->
<string name="state_focus">Focus!</string> <string name="create_session_not_possible_yet">Creating sessions still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. -->
<plurals name="state_focus_remaining">
<item quantity="zero">Focus one more time!</item> <!-- Add Timer -->
<item quantity="one">Focus! (%d break remaining)</item> <string name="addTimer_description_error">Timer description cannot be empty!</string>
<item quantity="other">Focus! (%d breaks remaining)</item> <string name="addTimer_description">Timer description</string>
</plurals> <string name="addTimer_name_error">Timer name cannot be empty!</string>
<string name="state_done">Done!</string> <string name="addTimer_name">Timer name</string>
<string name="state_take_a_break">Take a break!</string> <string name="addTimer_timepicker">Open Time Picker</string>
<string name="breakTime_1">" hours and "</string>
<string name="breakTime_2">" minutes of breaktime"</string>
<string name="addTimer_break_1">" break"</string>
<string name="addTimer_break_s">" breaks"</string>
<string name="addTimer_break_question">With breaks?</string>
<string name="addTimer_studytime_1">" hours and "</string>
<string name="addTimer_studytime_2">" minutes of studytime"</string>
<string name="addTimer_question">How long do you want to study?</string>
<!-- Edit Timer--> <!-- Edit Timer-->
<string name="name">Name</string> <string name="name">Name</string>
@ -82,11 +130,4 @@
<string name="breakTime">Break Time</string> <string name="breakTime">Break Time</string>
<string name="repeats">Number of Repeats</string> <string name="repeats">Number of Repeats</string>
<!-- Settings -->
<string name="settings">Settings</string>
<!-- About -->
<string name="about">About Studeez</string>
</resources> </resources>

View file

@ -2,6 +2,7 @@ package be.ugent.sel.studeez.timer_functional
import android.media.MediaPlayer import android.media.MediaPlayer
import be.ugent.sel.studeez.data.SelectedTimerState 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.FunctionalCustomTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
@ -24,7 +25,7 @@ class InvisibleSessionManagerTest {
@Test @Test
fun InvisibleEndlessTimerTest() = runTest { fun InvisibleEndlessTimerTest() = runTest {
timerState.selectedTimer = FunctionalEndlessTimer() timerState.selectedTimer = FunctionalEndlessTimer()
viewModel = SessionViewModel(timerState, mock()) viewModel = SessionViewModel(timerState, SessionReportState(), mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer) InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch { val test = launch {
@ -46,7 +47,7 @@ class InvisibleSessionManagerTest {
val breakTime = 5 val breakTime = 5
val repeats = 1 val repeats = 1
timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats) timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats)
viewModel = SessionViewModel(timerState, mock()) viewModel = SessionViewModel(timerState, SessionReportState(), mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer) InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch { val test = launch {
@ -79,7 +80,7 @@ class InvisibleSessionManagerTest {
@Test @Test
fun InvisibleCustomTimerTest() = runTest { fun InvisibleCustomTimerTest() = runTest {
timerState.selectedTimer = FunctionalCustomTimer(5) timerState.selectedTimer = FunctionalCustomTimer(5)
viewModel = SessionViewModel(timerState, mock()) viewModel = SessionViewModel(timerState, SessionReportState(), mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer) InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch { val test = launch {