Merge branch 'development' into addTimer

This commit is contained in:
Tibo De Peuter 2023-05-03 22:46:39 +02:00 committed by GitHub Enterprise
commit 3e721be32c
37 changed files with 694 additions and 193 deletions

View file

@ -2,18 +2,8 @@ package be.ugent.sel.studeez
import android.content.res.Resources
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
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.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
@ -38,6 +28,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.timer_overview.TimerOverviewRoute
@ -92,7 +84,7 @@ fun resources(): Resources {
@Composable
fun StudeezNavGraph(
appState: StudeezAppstate,
modifier: Modifier,
modifier: Modifier = Modifier,
) {
val drawerViewModel: DrawerViewModel = hiltViewModel()
val navBarViewModel: NavigationBarViewModel = hiltViewModel()
@ -114,8 +106,52 @@ fun StudeezNavGraph(
startDestination = StudeezDestinations.SPLASH_SCREEN,
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) {
SplashRoute(
openAndPopUp,
@ -137,32 +173,12 @@ fun StudeezNavGraph(
)
}
composable(StudeezDestinations.HOME_SCREEN) {
HomeRoute(
// Studying flow
composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open,
goBack,
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
)
}
@ -174,26 +190,6 @@ fun StudeezNavGraph(
)
}
// TODO Timers screen
// TODO Settings screen
// Edit screens
composable(StudeezDestinations.EDIT_PROFILE_SCREEN) {
EditProfileRoute(
goBack,
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open,
goBack,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SESSION_RECAP) {
SessionRecapRoute(
openAndPopUp = openAndPopUp,
@ -208,5 +204,27 @@ fun StudeezNavGraph(
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
}
composable(StudeezDestinations.EDIT_PROFILE_SCREEN) {
EditProfileRoute(
goBack,
openAndPopUp,
viewModel = hiltViewModel(),
)
}
}
}

View file

@ -2,7 +2,6 @@ package be.ugent.sel.studeez.common.composable
import androidx.annotation.StringRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -13,6 +12,7 @@ import androidx.compose.ui.unit.sp
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.card
import be.ugent.sel.studeez.common.ext.defaultButtonShape
@Composable
fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) {
@ -37,7 +37,7 @@ fun BasicButton(
Button(
onClick = onClick,
modifier = modifier,
shape = RoundedCornerShape(20.dp),
shape = defaultButtonShape(),
colors = colors,
border = border,
) {
@ -54,6 +54,25 @@ fun BasicButtonPreview() {
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
fun StealthButton(
@StringRes text: Int,

View file

@ -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({}, {}, {})
) }
}

View file

@ -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(

View file

@ -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()

View file

@ -0,0 +1,48 @@
package be.ugent.sel.studeez.common.composable
import android.app.TimePickerDialog
import android.app.TimePickerDialog.OnTimeSetListener
import android.content.Context
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import java.util.*
@Composable
fun TimePickerButton(
hoursMinutesSeconds: HoursMinutesSeconds,
modifier: Modifier = Modifier,
colors: ButtonColors = ButtonDefaults.buttonColors(),
border: BorderStroke? = null,
onTimeSetListener: OnTimeSetListener
) {
val context = LocalContext.current
Button(
onClick = { pickDuration(context, onTimeSetListener) },
modifier = modifier,
shape = RoundedCornerShape(20.dp),
colors = colors,
border = border
) {
Text(text = hoursMinutesSeconds.toString())
}
}
private fun pickDuration(context: Context, listener: OnTimeSetListener) {
val timePickerDialog = TimePickerDialog(
context,
listener,
0,
0,
true
)
timePickerDialog.show()
}

View file

@ -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(

View file

@ -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)
}
}

View file

@ -14,16 +14,24 @@ 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.TASKS_SCREEN
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme
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(
@ -35,6 +43,7 @@ fun getNavigationBarActions(
isSelectedTab = { screen ->
screen == getCurrentScreen()
},
onHomeClick = {
navigationBarViewModel.onHomeClick(open)
},
@ -47,6 +56,16 @@ fun getNavigationBarActions(
onProfileClick = {
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)) },
// TODO selected = navigationBarActions.isSelectedTab(TASKS_SCREEN),
selected = false,
selected = navigationBarActions.isSelectedTab(TASKS_SCREEN),
onClick = navigationBarActions.onTasksClick
)
// 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 = {
@ -86,8 +104,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
)
@ -110,7 +127,7 @@ fun NavigationBar(
fun NavigationBarPreview() {
StudeezTheme {
NavigationBar(
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
)
}
}

View file

@ -1,12 +1,16 @@
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.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.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(
@ -19,14 +23,29 @@ class NavigationBarViewModel @Inject constructor(
}
fun onTasksClick(open: (String) -> Unit) {
// TODO
open(TASKS_SCREEN)
}
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
}
}

View file

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

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.data
class EditTimerState {
}

View file

@ -1,4 +1,15 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
data class HoursMinutesSeconds(val hours: String, val minutes: String, val seconds: String
)
data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) {
fun getTotalSeconds(): Int {
return hours * 60 * 60 + minutes * 60 + seconds
}
override fun toString(): String {
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"
}
}

View file

@ -17,11 +17,7 @@ class Time(initialTime: Int) {
val minutes: Int = (time / (60)) % 60
val seconds: Int = time % 60
return HoursMinutesSeconds(
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0')
)
return HoursMinutesSeconds(hours, minutes, seconds)
}
}

View file

@ -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 <T> accept(visitor: TimerInfoVisitor<T>): T {
return visitor.visitCustomTimerInfo(this)
}
}

View file

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

View file

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

View file

@ -7,8 +7,8 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
*/
abstract class TimerInfo(
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<String, Any>
abstract fun <T> accept(visitor: TimerInfoVisitor<T>): T
}

View file

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

View file

@ -1,23 +1,32 @@
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 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 SESSION_SCREEN = "session"
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"
// const val SETTINGS_SCREEN = "settings"
// Friends flow
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 ADD_TIMER_SCREEN = "add_timer"

View file

@ -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 }, {}, {}, {}, {}, {}, {}, {})
)
}

View file

@ -63,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,

View file

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

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

@ -0,0 +1,35 @@
package be.ugent.sel.studeez.screens.timer_edit
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
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.ext.basicButton
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
class GetTimerEditView: TimerInfoVisitor<Unit> {
@SuppressLint("ComposableNaming")
override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo) {
}
@SuppressLint("ComposableNaming")
override fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo) {
}
@SuppressLint("ComposableNaming")
override fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo) {
}
}

View file

@ -0,0 +1,2 @@
package be.ugent.sel.studeez.screens.timer_edit

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit
class TimerEditViewModel {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit
abstract class AbstractTimerEditScreen {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
class BreakTimerEditScreen {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit
class CustomTimerEditScreen {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
class EndlessTimerEditScreen {
}

View file

@ -64,6 +64,14 @@ fun TimerOverviewScreen(
drawerActions = drawerActions
) {
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) {}

View file

@ -1,14 +1,20 @@
package be.ugent.sel.studeez.screens.timer_selection
import android.widget.TimePicker
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.LocalContext
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 +23,8 @@ import kotlinx.coroutines.flow.flowOf
data class TimerSelectionActions(
val getAllTimers: () -> Flow<List<TimerInfo>>,
val startSession: (TimerInfo) -> Unit,
val pickDuration: (TimePicker?, Int, Int) -> Unit,
val customTimeStudyTime: Int
)
fun getTimerSelectionActions(
@ -26,6 +34,10 @@ fun getTimerSelectionActions(
return TimerSelectionActions(
getAllTimers = viewModel::getAllTimers,
startSession = { viewModel.startSession(open, it) },
pickDuration = { _, hour: Int, minute: Int ->
viewModel.customTimerStudyTime.value = hour * 60 * 60 + minute * 60
},
customTimeStudyTime = viewModel.customTimerStudyTime.value
)
}
@ -52,6 +64,11 @@ fun TimerSelectionScreen(
popUp = popUp
) {
LazyColumn {
// Custom timer with duration selection button
item {
CustomTimerEntry(timerSelectionActions)
}
// All timers
items(timers.value) { timerInfo ->
TimerEntry(
@ -68,11 +85,39 @@ 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(
hoursMinutesSeconds = hms,
onTimeSetListener = timerSelectionActions.pickDuration
)
}
)
}
@Preview
@Composable
fun TimerSelectionPreview() {
TimerSelectionScreen(
timerSelectionActions = TimerSelectionActions({ flowOf() }, {}),
timerSelectionActions = TimerSelectionActions({ flowOf() }, {}, { _, _, _ -> {} }, 0),
popUp = {}
)
}

View file

@ -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<Int> = mutableStateOf(0)
fun getAllTimers() : Flow<List<TimerInfo>> {
return timerDAO.getAllTimers()
}

View file

@ -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(

View file

@ -1,5 +1,6 @@
<resources>
<!-- Common -->
<!-- ========== Common ========== -->
<string name="app_name">Studeez</string>
<string name="username">Username</string>
<string name="email">Email</string>
@ -7,20 +8,75 @@
<string name="repeat_password">Repeat password</string>
<string name="menu">Menu</string>
<!-- Actions -->
<string name="confirm">Confirm</string>
<string name="save">Save</string>
<string name="discard">Discard</string>
<string name="cancel">Cancel</string>
<string name="go_back">Go back</string>
<string name="next">Next</string>
<string name="start">Start</string>
<!-- Actions -->
<string name="confirm">Confirm</string>
<string name="save">Save</string>
<string name="discard">Discard</string>
<string name="cancel">Cancel</string>
<string name="go_back">Go back</string>
<string name="next">Next</string>
<string name="start">Start</string>
<!-- Messages -->
<string name="success">Success!</string>
<string name="try_again">Try again</string>
<string name="generic_error">Something wrong happened. Please try again.</string>
<string name="email_error">Please insert a valid email.</string>
<!-- Messages -->
<string name="success">Success!</string>
<string name="try_again">Try again</string>
<string name="generic_error">Something wrong happened. Please try again.</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 -->
<string name="create_account">Create account</string>
@ -36,51 +92,22 @@
<string name="recovery_email_sent">Check your inbox for the recovery email.</string>
<string name="empty_password_error">Password cannot be empty.</string>
<!-- HomeScreen -->
<string name="home">Home</string>
<string name="start_session">Start session</string>
<!-- ========== Studying flow ========== -->
<!-- Tasks -->
<string name="tasks">Tasks</string>
<!-- ========== Friends flow ========== -->
<!-- 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="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 -->
<string name="log_out">Log out</string>
<string name="profile_picture_description">Profile Picture</string>
<string name="user_description">Normal user</string>
<!-- ========== Create & edit screens ========== -->
<!-- Timers -->
<string name="timers">Timers</string>
<string name="edit">Edit</string>
<string name="add_timer">Add timer</string>
<string name="state_focus">Focus!</string>
<plurals name="state_focus_remaining">
<item quantity="zero">Focus one more time!</item>
<item quantity="one">Focus! (%d break remaining)</item>
<item quantity="other">Focus! (%d breaks remaining)</item>
</plurals>
<string name="state_done">Done!</string>
<string name="state_take_a_break">Take a break!</string>
<!-- Settings -->
<string name="settings">Settings</string>
<!-- About -->
<string name="about">About Studeez</string>
<!-- Task -->
<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. -->
<!-- Session -->
<string name="create_session_not_possible_yet">Creating sessions still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. -->
<!-- Add Timer -->
<string name="addTimer_description_error">Timer description cannot be empty!</string>
<string name="addTimer_description">Timer description</string>