merged with current dev

This commit is contained in:
Rune Dyselinck 2023-04-25 16:45:00 +02:00
commit b30edf6538
22 changed files with 849 additions and 373 deletions

View file

@ -1,110 +0,0 @@
package be.ugent.sel.studeez.screens.drawer
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
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.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme
@Composable
fun Drawer(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: DrawerViewModel = hiltViewModel()
) {
Column (
modifier = Modifier.fillMaxWidth()
) {
Column (
modifier = Modifier.fillMaxWidth().weight(1f)
) {
DrawerEntry(
icon = Icons.Default.Home,
text = resources().getString(R.string.home)
) {
viewModel.onHomeButtonClick(open)
}
DrawerEntry(
icon = ImageVector.vectorResource(id = R.drawable.ic_timer),
text = resources().getString(R.string.timers)
) {
viewModel.onTimersClick(open)
}
DrawerEntry(
icon = Icons.Default.Settings,
text = resources().getString(R.string.settings)
) {
viewModel.onSettingsClick(open)
}
DrawerEntry(
icon = ImageVector.vectorResource(id = R.drawable.ic_logout),
text = resources().getString(R.string.log_out)
) {
viewModel.onLogoutClick(openAndPopUp)
}
}
DrawerEntry(
icon = Icons.Outlined.Info,
text = resources().getString(R.string.about)
) {
viewModel.onAboutClick(open)
}
}
}
@Composable
fun DrawerEntry(
icon: ImageVector,
text: String,
onClick: () -> Unit
) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clickable(onClick = { onClick() })
.fillMaxWidth()
) {
Box(
modifier = Modifier
.padding(vertical = 12.dp)
.fillMaxWidth(0.15f)
) {
Icon(imageVector = icon, contentDescription = text)
}
Box(
modifier = Modifier
.padding(vertical = 12.dp)
.fillMaxWidth(0.85f)
) {
Text(text = text)
}
}
}
@Preview
@Composable
fun DrawerPreview() {
StudeezTheme {
Drawer(
{ _, -> {} },
{ _, _ -> {} },
hiltViewModel()
)
}
}

View file

@ -1,40 +0,0 @@
package be.ugent.sel.studeez.screens.drawer
import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
import be.ugent.sel.studeez.navigation.StudeezDestinations.LOGIN_SCREEN
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class DrawerViewModel @Inject constructor(
private val accountDAO: AccountDAO,
logService: LogService
) : StudeezViewModel(logService) {
fun onHomeButtonClick(open: (String) -> Unit) {
open(HOME_SCREEN)
}
fun onTimersClick(openAndPopup: (String) -> Unit) {
openAndPopup(StudeezDestinations.TIMER_OVERVIEW_SCREEN)
}
fun onSettingsClick(open: (String) -> Unit) {
// TODO
}
fun onLogoutClick(openAndPopUp: (String, String) -> Unit) {
launchCatching {
accountDAO.signOut()
openAndPopUp(LOGIN_SCREEN, HOME_SCREEN)
}
}
fun onAboutClick(open: (String) -> Unit) {
// TODO
}
}

View file

@ -6,37 +6,69 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel
import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.resources
@Composable
fun HomeScreen(
fun HomeRoute(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: HomeViewModel = hiltViewModel()
viewModel: HomeViewModel,
drawerViewModel: DrawerViewModel,
navBarViewModel: NavigationBarViewModel,
) {
HomeScreen(
onStartSessionClick = { viewModel.onStartSessionClick(open) },
drawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp),
navigationBarActions = getNavigationBarActions(navBarViewModel, open),
)
}
@Composable
fun HomeScreen(
onStartSessionClick: () -> Unit,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions,
) {
PrimaryScreenTemplate(
title = resources().getString(R.string.home),
open = open,
openAndPopUp = openAndPopUp,
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
action = { FriendsAction() }
) {
BasicButton(R.string.start_session, Modifier.basicButton()) {
viewModel.onStartSessionClick(open)
onStartSessionClick()
}
}
}
@Composable
fun FriendsAction () {
fun FriendsAction() {
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Default.Person,
contentDescription = resources().getString(R.string.friends)
)
}
}
}
@Preview
@Composable
fun HomeScreenPreview() {
HomeScreen(
onStartSessionClick = {},
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({}, {}, {}, {})
)
}

View file

@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.*
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.fieldModifier
@ -18,14 +18,48 @@ import be.ugent.sel.studeez.common.ext.textButton
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
data class LoginScreenActions(
val onEmailChange: (String) -> Unit,
val onPasswordChange: (String) -> Unit,
val onSignUpClick: () -> Unit,
val onSignInClick: () -> Unit,
val onForgotPasswordClick: () -> Unit,
)
fun getLoginScreenActions(
viewModel: LoginViewModel,
openAndPopUp: (String, String) -> Unit,
): LoginScreenActions {
return LoginScreenActions(
onEmailChange = { viewModel.onEmailChange(it) },
onPasswordChange = { viewModel.onPasswordChange(it) },
onSignUpClick = { viewModel.onSignUpClick(openAndPopUp) },
onSignInClick = { viewModel.onSignInClick(openAndPopUp) },
onForgotPasswordClick = { viewModel.onForgotPasswordClick() }
)
}
@Composable
fun LoginScreen(
fun LoginRoute(
openAndPopUp: (String, String) -> Unit,
modifier: Modifier = Modifier,
viewModel: LoginViewModel = hiltViewModel()
viewModel: LoginViewModel,
) {
val uiState by viewModel.uiState
LoginScreen(
modifier = modifier,
uiState = uiState,
loginScreenActions = getLoginScreenActions(viewModel = viewModel, openAndPopUp)
)
}
@Composable
fun LoginScreen(
modifier: Modifier = Modifier,
uiState: LoginUiState,
loginScreenActions: LoginScreenActions,
) {
SimpleScreenTemplate(title = resources().getString(AppText.sign_in)) {
Column(
modifier = modifier
@ -35,18 +69,42 @@ fun LoginScreen(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
EmailField(uiState.email, viewModel::onEmailChange, Modifier.fieldModifier())
PasswordField(uiState.password, viewModel::onPasswordChange, Modifier.fieldModifier())
EmailField(
uiState.email,
loginScreenActions.onEmailChange,
Modifier.fieldModifier()
)
PasswordField(
uiState.password,
loginScreenActions.onPasswordChange,
Modifier.fieldModifier()
)
BasicButton(
AppText.sign_in,
Modifier.basicButton(),
onClick = loginScreenActions.onSignInClick,
)
BasicButton(AppText.sign_in, Modifier.basicButton()) { viewModel.onSignInClick(openAndPopUp) }
BasicTextButton(
AppText.not_already_user,
Modifier.textButton(),
action = loginScreenActions.onSignUpClick,
)
BasicTextButton(AppText.not_already_user, Modifier.textButton()) {
viewModel.onNotAlreadyUser(openAndPopUp)
}
BasicTextButton(AppText.forgot_password, Modifier.textButton()) {
viewModel.onForgotPasswordClick()
}
BasicTextButton(
AppText.forgot_password,
Modifier.textButton(),
action = loginScreenActions.onForgotPasswordClick,
)
}
}
}
@Preview
@Composable
fun LoginScreenPreview() {
LoginScreen(
uiState = LoginUiState(),
loginScreenActions = LoginScreenActions({}, {}, {}, {}, {})
)
}

View file

@ -63,7 +63,7 @@ class LoginViewModel @Inject constructor(
}
}
fun onNotAlreadyUser(openAndPopUp: (String, String) -> Unit) {
fun onSignUpClick(openAndPopUp: (String, String) -> Unit) {
openAndPopUp(SIGN_UP_SCREEN, LOGIN_SCREEN)
}
}

View file

@ -1,75 +0,0 @@
package be.ugent.sel.studeez.screens.navbar
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.outlined.DateRange
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme
import be.ugent.sel.studeez.R.string as AppText
@Composable
fun NavigationBar(
open: (String) -> Unit,
viewModel: NavigationBarViewModel = hiltViewModel()
) {
// TODO Pass functions and new screens.
// TODO Pass which screen is selected.
// TODO Disabled -> HIGH/MEDIUM_EMPHASIS if the page is implemented
BottomNavigation(
elevation = 10.dp
) {
BottomNavigationItem(
icon = { Icon(imageVector = Icons.Default.List, resources().getString(AppText.home)) },
label = { Text(text = resources().getString(AppText.home)) },
selected = false, // TODO
onClick = { viewModel.onHomeClick(open) }
)
BottomNavigationItem(
icon = { Icon(imageVector = Icons.Default.Check, resources().getString(AppText.tasks)) },
label = { Text(text = resources().getString(AppText.tasks)) },
selected = false, // TODO
onClick = { viewModel.onTasksClick(open) }
)
// Hack to space the entries in the navigation bar, make space for fab
BottomNavigationItem(icon = {}, onClick = {}, selected = false)
BottomNavigationItem(
icon = { Icon(imageVector = Icons.Outlined.DateRange, resources().getString(AppText.sessions)) },
label = { Text(text = resources().getString(AppText.sessions)) },
selected = false, // TODO
onClick = { viewModel.onSessionsClick(open) }
)
BottomNavigationItem(
icon = { Icon(imageVector = Icons.Default.Person, resources().getString(AppText.profile)) },
label = { Text(text = resources().getString(AppText.profile)) },
selected = false, // TODO
onClick = { viewModel.onProfileClick(open) }
)
}
}
@Preview(showBackground = true)
@Composable
fun NavigationBarPreview() {
StudeezTheme {
NavigationBar(
{ _ -> {} },
hiltViewModel()
)
}
}

View file

@ -1,32 +0,0 @@
package be.ugent.sel.studeez.screens.navbar
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.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class NavigationBarViewModel @Inject constructor(
private val accountDAO: AccountDAO,
logService: LogService
) : StudeezViewModel(logService) {
fun onHomeClick(open: (String) -> Unit) {
open(HOME_SCREEN)
}
fun onTasksClick(open: (String) -> Unit) {
// TODO
}
fun onSessionsClick(open: (String) -> Unit) {
// TODO
}
fun onProfileClick(open: (String) -> Unit) {
open(PROFILE_SCREEN)
}
}

View file

@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicTextButton
import be.ugent.sel.studeez.common.composable.LabelledInputField
@ -14,14 +13,43 @@ import be.ugent.sel.studeez.common.ext.textButton
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme
data class EditProfileActions(
val onUserNameChange: (String) -> Unit,
val onSaveClick: () -> Unit,
val onDeleteClick: () -> Unit
)
fun getEditProfileActions(
viewModel: ProfileEditViewModel,
openAndPopUp: (String, String) -> Unit,
): EditProfileActions {
return EditProfileActions(
onUserNameChange = { viewModel.onUsernameChange(it) },
onSaveClick = { viewModel.onSaveClick() },
onDeleteClick = { viewModel.onDeleteClick(openAndPopUp) },
)
}
@Composable
fun EditProfileRoute(
goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: ProfileEditViewModel,
) {
val uiState by viewModel.uiState
EditProfileScreen(
goBack = goBack,
uiState = uiState,
editProfileActions = getEditProfileActions(viewModel, openAndPopUp)
)
}
@Composable
fun EditProfileScreen(
goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: ProfileEditViewModel = hiltViewModel()
uiState: ProfileEditUiState,
editProfileActions: EditProfileActions,
) {
val uiState by viewModel.uiState
SecondaryScreenTemplate(
title = resources().getString(R.string.editing_profile),
popUp = goBack
@ -29,16 +57,19 @@ fun EditProfileScreen(
Column {
LabelledInputField(
value = uiState.username,
onNewValue = viewModel::onUsernameChange,
onNewValue = editProfileActions.onUserNameChange,
label = R.string.username
)
BasicTextButton(text = R.string.save, Modifier.textButton()) {
viewModel.onSaveClick()
}
BasicTextButton(text = R.string.delete_profile, Modifier.textButton()) {
viewModel.onDeleteClick(openAndPopUp)
}
BasicTextButton(
text = R.string.save,
Modifier.textButton(),
action = editProfileActions.onSaveClick
)
BasicTextButton(
text = R.string.delete_profile,
Modifier.textButton(),
action = editProfileActions.onDeleteClick
)
}
}
}
@ -47,9 +78,6 @@ fun EditProfileScreen(
@Composable
fun EditProfileScreenComposable() {
StudeezTheme {
EditProfileScreen (
{},
{_, _ -> {}}
)
EditProfileScreen({}, ProfileEditUiState(), EditProfileActions({}, {}, {}))
}
}

View file

@ -4,30 +4,68 @@ import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.Headline
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
import kotlinx.coroutines.CoroutineScope
import be.ugent.sel.studeez.R.string as AppText
data class ProfileActions(
val getUsername: suspend CoroutineScope.() -> String?,
val onEditProfileClick: () -> Unit,
)
fun getProfileActions(
viewModel: ProfileViewModel,
open: (String) -> Unit,
): ProfileActions {
return ProfileActions(
getUsername = { viewModel.getUsername() },
onEditProfileClick = { viewModel.onEditProfileClick(open) },
)
}
@Composable
fun ProfileRoute(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: ProfileViewModel,
) {
ProfileScreen(
profileActions = getProfileActions(viewModel, open),
drawerActions = getDrawerActions(hiltViewModel(), open, openAndPopUp),
navigationBarActions = getNavigationBarActions(hiltViewModel(), open),
)
}
@Composable
fun ProfileScreen(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: ProfileViewModel = hiltViewModel()
profileActions: ProfileActions,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions,
) {
var username: String? by remember { mutableStateOf("") }
LaunchedEffect(key1 = Unit) {
username = viewModel.getUsername()
username = profileActions.getUsername(this)
}
PrimaryScreenTemplate(
title = resources().getString(AppText.profile),
open = open,
openAndPopUp = openAndPopUp,
action = { EditAction { viewModel.onEditProfileClick(open) } }
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
action = { EditAction(onClick = profileActions.onEditProfileClick) }
) {
Headline(text = (username ?: resources().getString(R.string.no_username)))
}
@ -44,4 +82,14 @@ fun EditAction(
)
}
}
@Preview
@Composable
fun ProfileScreenPreview() {
ProfileScreen(
profileActions = ProfileActions({ null }, {}),
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({}, {}, {}, {})
)
}

View file

@ -24,34 +24,71 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
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.navigation.StudeezDestinations
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
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.FunctionalTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer.StudyState
import be.ugent.sel.studeez.resources
import kotlinx.coroutines.delay
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds
data class SessionActions(
val getTimer: () -> FunctionalTimer,
val getTask: () -> String,
val releaseMediaPlayer: () -> Unit,
val startMediaPlayer: () -> Unit,
)
fun getSessionActions(
viewModel: SessionViewModel,
mediaplayer: MediaPlayer,
): SessionActions {
return SessionActions(
getTimer = viewModel::getTimer,
getTask = viewModel::getTask,
releaseMediaPlayer = mediaplayer::release,
startMediaPlayer = mediaplayer::start,
)
}
@Composable
fun SessionScreen(
fun SessionRoute(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: SessionViewModel = hiltViewModel()
viewModel: SessionViewModel,
) {
val context = LocalContext.current
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val mediaplayer = MediaPlayer.create(context, uri)
mediaplayer.isLooping = false
InvisibleSessionManager.setParameters(viewModel = viewModel, mediaplayer = mediaplayer)
InvisibleSessionManager.setParameters(
viewModel = viewModel,
mediaplayer = mediaplayer
)
SessionScreen(
open = open,
sessionActions = getSessionActions(viewModel, mediaplayer)
)
}
@Composable
fun SessionScreen(
open: (String) -> Unit,
sessionActions: SessionActions
) {
Column(
modifier = Modifier.padding(10.dp)
) {
Timer(viewModel, mediaplayer)
modifier = Modifier.padding(10.dp)
) {
Timer(
sessionActions = sessionActions
)
Box(
contentAlignment = Alignment.Center,
@ -61,12 +98,11 @@ fun SessionScreen(
) {
TextButton(
onClick = {
mediaplayer.stop()
mediaplayer.release()
sessionActions.releaseMediaPlayer
open(StudeezDestinations.HOME_SCREEN)
InvisibleSessionManager.isSession = false
// Vanaf hier ook naar report gaan als "end session" knop word ingedrukt
},
},
modifier = Modifier
.padding(horizontal = 20.dp)
.border(1.dp, Color.Red, RoundedCornerShape(32.dp))
@ -77,7 +113,8 @@ fun SessionScreen(
color = Color.Red,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
modifier = Modifier.padding(1.dp)
modifier = Modifier
.padding(1.dp)
)
}
}
@ -85,19 +122,21 @@ fun SessionScreen(
}
@Composable
private fun Timer(viewModel: SessionViewModel = hiltViewModel(), mediaplayer: MediaPlayer) {
private fun Timer(
sessionActions: SessionActions
) {
var tikker by remember { mutableStateOf(false) }
LaunchedEffect(tikker) {
delay(1.seconds)
viewModel.getTimer().tick()
sessionActions.getTimer().tick()
tikker = !tikker
}
if (viewModel.getTimer().hasCurrentCountdownEnded() && !viewModel.getTimer().hasEnded()) {
mediaplayer.start()
if (sessionActions.getTimer().hasCurrentCountdownEnded() && !sessionActions.getTimer().hasEnded()) {
sessionActions.startMediaPlayer
}
val hms = viewModel.getTimer().getHoursMinutesSeconds()
val hms = sessionActions.getTimer().getHoursMinutesSeconds()
Column {
Text(
text = "${hms.hours} : ${hms.minutes} : ${hms.seconds}",
@ -108,14 +147,14 @@ private fun Timer(viewModel: SessionViewModel = hiltViewModel(), mediaplayer: Me
fontWeight = FontWeight.Bold,
fontSize = 40.sp,
)
val stateString: String = when (viewModel.getTimer().view) {
val stateString: String = when (sessionActions.getTimer().view) {
StudyState.DONE -> resources().getString(R.string.state_done)
StudyState.FOCUS -> resources().getString(R.string.state_focus)
StudyState.BREAK -> resources().getString(R.string.state_take_a_break)
StudyState.FOCUS_REMAINING ->
(viewModel.getTimer() as FunctionalPomodoroTimer?)?.breaksRemaining?.let {
(sessionActions.getTimer() as FunctionalPomodoroTimer?)?.breaksRemaining?.let {
resources().getQuantityString(R.plurals.state_focus_remaining, it, it)
}.toString()
}.toString()
}
Text(
@ -139,7 +178,7 @@ private fun Timer(viewModel: SessionViewModel = hiltViewModel(), mediaplayer: Me
.background(Color.Blue, RoundedCornerShape(32.dp))
) {
Text(
text = viewModel.getTask(),
text = sessionActions.getTask(),
color = Color.White,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
@ -151,6 +190,8 @@ private fun Timer(viewModel: SessionViewModel = hiltViewModel(), mediaplayer: Me
}
}
@Singleton
object InvisibleSessionManager {
private lateinit var viewModel: SessionViewModel
private lateinit var mediaplayer: MediaPlayer
@ -174,3 +215,19 @@ object InvisibleSessionManager {
}
}
}
@Preview
@Composable
fun TimerPreview() {
Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}))
}
@Preview
@Composable
fun SessionPreview() {
SessionScreen(
open = {},
sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {})
)
}

View file

@ -10,23 +10,64 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.common.composable.*
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.BasicTextButton
import be.ugent.sel.studeez.common.composable.EmailField
import be.ugent.sel.studeez.common.composable.PasswordField
import be.ugent.sel.studeez.common.composable.RepeatPasswordField
import be.ugent.sel.studeez.common.composable.SimpleScreenTemplate
import be.ugent.sel.studeez.common.composable.UsernameField
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.common.ext.textButton
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
data class SignUpActions(
val onUserNameChange: (String) -> Unit,
val onEmailChange: (String) -> Unit,
val onPasswordChange: (String) -> Unit,
val onRepeatPasswordChange: (String) -> Unit,
val onSignUpClick: () -> Unit,
val onLoginClick: () -> Unit,
)
fun getSignUpActions(
viewModel: SignUpViewModel,
openAndPopUp: (String, String) -> Unit,
): SignUpActions {
return SignUpActions(
onUserNameChange = { viewModel.onUsernameChange(it) },
onEmailChange = { viewModel.onEmailChange(it) },
onPasswordChange = { viewModel.onPasswordChange(it) },
onRepeatPasswordChange = { viewModel.onRepeatPasswordChange(it) },
onSignUpClick = { viewModel.onSignUpClick(openAndPopUp) },
onLoginClick = { viewModel.onLoginClick(openAndPopUp) },
)
}
@Composable
fun SignUpScreen(
fun SignUpRoute(
openAndPopUp: (String, String) -> Unit,
modifier: Modifier = Modifier,
viewModel: SignUpViewModel = hiltViewModel()
viewModel: SignUpViewModel,
) {
val uiState by viewModel.uiState
val fieldModifier = Modifier.fieldModifier()
SignUpScreen(
modifier = modifier,
uiState,
getSignUpActions(viewModel, openAndPopUp)
)
}
@Composable
fun SignUpScreen(
modifier: Modifier = Modifier,
uiState: SignUpUiState,
signUpActions: SignUpActions,
) {
val fieldModifier = Modifier.fieldModifier()
SimpleScreenTemplate(title = resources().getString(AppText.create_account)) {
Column(
modifier = modifier
@ -36,40 +77,45 @@ fun SignUpScreen(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
UsernameField(
uiState.username,
viewModel::onUsernameChange,
signUpActions.onUserNameChange,
fieldModifier
)
EmailField(
uiState.email,
viewModel::onEmailChange,
signUpActions.onEmailChange,
fieldModifier
)
PasswordField(
uiState.password,
viewModel::onPasswordChange,
signUpActions.onPasswordChange,
fieldModifier
)
RepeatPasswordField(
uiState.repeatPassword,
viewModel::onRepeatPasswordChange,
signUpActions.onRepeatPasswordChange,
fieldModifier
)
BasicButton(AppText.create_account, Modifier.basicButton()) {
viewModel.onSignUpClick(openAndPopUp)
}
BasicTextButton(AppText.already_user, Modifier.textButton()) {
viewModel.onLoginScreenClick(openAndPopUp)
}
BasicButton(
AppText.create_account,
Modifier.basicButton(),
onClick = signUpActions.onSignUpClick
)
BasicTextButton(
AppText.already_user,
Modifier.textButton(),
action = signUpActions.onLoginClick
)
}
}
}
@Preview
@Composable
fun SignUpPreview() {
SignUpScreen(
uiState = SignUpUiState(),
signUpActions = SignUpActions({}, {}, {}, {}, {}, {})
)
}

View file

@ -71,7 +71,7 @@ class SignUpViewModel @Inject constructor(
}
}
fun onLoginScreenClick(openAndPopUp: (String, String) -> Unit) {
fun onLoginClick(openAndPopUp: (String, String) -> Unit) {
openAndPopUp(LOGIN_SCREEN, SIGN_UP_SCREEN)
}
}

View file

@ -15,7 +15,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.ext.basicButton
import kotlinx.coroutines.delay
@ -24,14 +24,26 @@ import be.ugent.sel.studeez.R.string as AppText
private const val SPLASH_TIMEOUT = 500L
@Composable
fun SplashScreen(
fun SplashRoute(
openAndPopUp: (String, String) -> Unit,
modifier: Modifier = Modifier,
viewModel: SplashViewModel = hiltViewModel()
viewModel: SplashViewModel,
) {
SplashScreen(
modifier = modifier,
onAppStart = { viewModel.onAppStart(openAndPopUp) },
showError = viewModel.showError.value
)
}
@Composable
fun SplashScreen(
modifier: Modifier = Modifier,
onAppStart: () -> Unit,
showError: Boolean,
) {
Column(
modifier =
modifier
modifier = modifier
.fillMaxWidth()
.fillMaxHeight()
.background(color = MaterialTheme.colors.background)
@ -39,17 +51,37 @@ fun SplashScreen(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (viewModel.showError.value) {
if (showError) {
Text(text = stringResource(AppText.generic_error))
BasicButton(AppText.try_again, Modifier.basicButton()) { viewModel.onAppStart(openAndPopUp) }
BasicButton(
AppText.try_again,
Modifier.basicButton(),
onClick = onAppStart,
)
} else {
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
}
}
LaunchedEffect(true) {
delay(SPLASH_TIMEOUT)
viewModel.onAppStart(openAndPopUp)
onAppStart()
}
}
@Preview
@Composable
fun SplashPreview() {
SplashScreen(
onAppStart = {},
showError = false,
)
}
@Preview
@Composable
fun SplashErrorPreview() {
SplashScreen(
onAppStart = {},
showError = true,
)
}

View file

@ -1,122 +1,118 @@
package be.ugent.sel.studeez.screens.timer_overview
import android.annotation.SuppressLint
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.composable.StealthButton
import be.ugent.sel.studeez.common.composable.TimerEntry
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel
import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.resources
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
data class TimerOverviewActions(
val getUserTimers: () -> Flow<List<TimerInfo>>,
val getDefaultTimers: () -> List<TimerInfo>,
val onEditClick: (TimerInfo) -> Unit,
)
fun getTimerOverviewActions(
viewModel: TimerOverviewViewModel,
): TimerOverviewActions {
return TimerOverviewActions(
getUserTimers = viewModel::getUserTimers,
getDefaultTimers = viewModel::getDefaultTimers,
onEditClick = { viewModel.update(it) },
)
}
@Composable
fun TimerOverviewRoute(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: TimerOverviewViewModel,
drawerViewModel: DrawerViewModel,
navBarViewModel: NavigationBarViewModel,
) {
TimerOverviewScreen(
timerOverviewActions = getTimerOverviewActions(viewModel),
drawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp),
navigationBarActions = getNavigationBarActions(navBarViewModel, open),
)
}
@Composable
fun TimerOverviewScreen(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: TimerOverviewViewModel = hiltViewModel()
timerOverviewActions: TimerOverviewActions,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions,
) {
val timers = viewModel.getUserTimers().collectAsState(initial = emptyList())
val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList())
// TODO moet geen primary screen zijn: geen navbar nodig
PrimaryScreenTemplate(
title = resources().getString(R.string.timers),
open = open,
openAndPopUp = openAndPopUp
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
) {
Column {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(7.dp)
) {
// Default Timers, cannot be edited
items(viewModel.getDefaultTimers()) {
TimerEntry(timerInfo = it, canDisplay = false)
items(timerOverviewActions.getDefaultTimers()) {
TimerEntry(timerInfo = it) {}
}
// User timers, can be edited
items(timers.value) {
TimerEntry(timerInfo = it, true, R.string.edit) { timerInfo ->
viewModel.update(timerInfo)
items(timers.value) { timerInfo ->
TimerEntry(
timerInfo = timerInfo,
) {
StealthButton(
text = R.string.edit,
onClick = { timerOverviewActions.onEditClick(timerInfo) }
)
}
}
}
BasicButton(R.string.add_timer, Modifier.basicButton()) {
// TODO
}
}
}
}
@Composable
fun TimerEntry(
timerInfo: TimerInfo,
canDisplay: Boolean,
@StringRes buttonName: Int = -1,
buttonFunction: (TimerInfo) -> Unit = {}
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(
Modifier.padding(horizontal = 10.dp)
) {
Text(
text = timerInfo.name,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
Text(
text = timerInfo.description,
fontWeight = FontWeight.Light,
fontSize = 15.sp
)
}
if (canDisplay) {
StealthButton(buttonName) {
buttonFunction(timerInfo)
}
}
}
}
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Preview
@Composable
fun TimerEntryPreview() {
val timerInfo = CustomTimerInfo(
"my preview timer",
"This is the description of the timer",
60
fun TimerOverviewPreview() {
val customTimer = CustomTimerInfo(
"my preview timer", "This is the description of the timer", 60
)
Scaffold() {
Column() {
TimerEntry(timerInfo = timerInfo, true, buttonName = R.string.edit) { }
TimerEntry(timerInfo = timerInfo, true, buttonName = R.string.edit) { }
TimerEntry(timerInfo = timerInfo, true, buttonName = R.string.edit) { }
}
}
}
TimerOverviewScreen(
timerOverviewActions = TimerOverviewActions(
{ flowOf() },
{ listOf(customTimer, customTimer) },
{}),
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({}, {}, {}, {})
)
}

View file

@ -3,42 +3,89 @@ package be.ugent.sel.studeez.screens.timer_selection
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.composable.StealthButton
import be.ugent.sel.studeez.common.composable.TimerEntry
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel
import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.screens.timer_overview.TimerEntry
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
data class TimerSelectionActions(
val getAllTimers: () -> Flow<List<TimerInfo>>,
val startSession: (TimerInfo) -> Unit,
)
fun getTimerSelectionActions(
viewModel: TimerSelectionViewModel,
open: (String) -> Unit,
): TimerSelectionActions {
return TimerSelectionActions(
getAllTimers = viewModel::getAllTimers,
startSession = { viewModel.startSession(open, it) },
)
}
@Composable
fun TimerSelectionRoute(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: TimerSelectionViewModel,
drawerViewModel: DrawerViewModel,
navBarViewModel: NavigationBarViewModel,
) {
TimerSelectionScreen(
timerSelectionActions = getTimerSelectionActions(viewModel, open),
drawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp),
navigationBarActions = getNavigationBarActions(navBarViewModel, open),
)
}
@Composable
fun TimerSelectionScreen(
open: (String) -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: TimerSelectionViewModel = hiltViewModel()
timerSelectionActions: TimerSelectionActions,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions,
) {
val timers = viewModel.getAllTimers().collectAsState(initial = emptyList())
val timers = timerSelectionActions.getAllTimers().collectAsState(initial = emptyList())
PrimaryScreenTemplate(
title = resources().getString(R.string.timers),
open = open,
openAndPopUp = openAndPopUp,
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(7.dp)) {
// All timers
items(timers.value) {
items(timers.value) { timerInfo ->
TimerEntry(
timerInfo = it,
canDisplay = true,
buttonName = R.string.start
) { timerInfo ->
viewModel.startSession(open, timerInfo)
timerInfo = timerInfo,
) {
StealthButton(
text = R.string.start,
onClick = { timerSelectionActions.startSession(timerInfo) }
)
}
}
}
}
}
@Preview
@Composable
fun TimerSelectionPreview() {
TimerSelectionScreen(
timerSelectionActions = TimerSelectionActions({ flowOf() }, {}),
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({}, {}, {}, {}),
)
}