diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..704c883 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt index 90f20fd..91cc208 100644 --- a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt +++ b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt @@ -33,6 +33,7 @@ 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 +import be.ugent.sel.studeez.screens.timer_overview.add_timer.AddTimerRoute import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute import be.ugent.sel.studeez.ui.theme.StudeezTheme import kotlinx.coroutines.CoroutineScope @@ -140,6 +141,7 @@ fun StudeezNavGraph( TimerOverviewRoute( viewModel = hiltViewModel(), drawerActions = drawerActions, + open = open ) } @@ -195,6 +197,14 @@ fun StudeezNavGraph( ) } + composable(StudeezDestinations.ADD_TIMER_SCREEN) { + AddTimerRoute( + open = open, + goBack = goBack, + viewModel = hiltViewModel() + ) + } + // Friends flow composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) { // TODO diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt index 9751750..ae675e5 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt @@ -16,7 +16,14 @@ import be.ugent.sel.studeez.common.ext.defaultButtonShape @Composable fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { - TextButton(onClick = action, modifier = modifier) { Text(text = stringResource(text)) } + TextButton( + onClick = action, + modifier = modifier + ) { + Text( + text = stringResource(text) + ) + } } @Composable diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/TimePickerComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/TimePickerComposable.kt new file mode 100644 index 0000000..3a59519 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/TimePickerComposable.kt @@ -0,0 +1,24 @@ +package be.ugent.sel.studeez.common.composable.navbar + +import android.app.TimePickerDialog +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +@Composable +fun BasicTimePicker( + onHoursChange: (Int) -> Unit, + onMinutesChange: (Int) -> Unit, + Hours: Int, + Minutes: Int, +): TimePickerDialog { + return TimePickerDialog( + LocalContext.current, + { _, mHour: Int, mMinute: Int -> + onHoursChange(mHour) + onMinutesChange(mMinute) + }, + Hours, + Minutes, + true + ) +} diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt index 0d55dec..b5bcfda 100644 --- a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt +++ b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt @@ -28,4 +28,6 @@ object StudeezDestinations { const val CREATE_TASK_SCREEN = "create_task" const val CREATE_SESSION_SCREEN = "create_session" const val EDIT_PROFILE_SCREEN = "edit_profile" + + const val ADD_TIMER_SCREEN = "add_timer" } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt index 2ddbc32..477d174 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt @@ -47,7 +47,9 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi val sessionReport: SessionReport = sessionRecapActions.getSessionReport() val studyTime: Int = sessionReport.studyTime val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() - Column { + Column( + modifier = modifier + ) { Text(text = "You studied: ${hms.hours} : ${hms.minutes} : ${hms.seconds}") BasicButton( diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt index e9ad6b1..5fb4943 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt @@ -11,7 +11,7 @@ import javax.inject.Inject @HiltViewModel class SessionRecapViewModel @Inject constructor( - private val sessionReportState: SessionReportState, + sessionReportState: SessionReportState, private val sessionDAO: SessionDAO, logService: LogService ) : StudeezViewModel(logService) { diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt index d6d7b07..f5b7c3b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt @@ -1,6 +1,5 @@ package be.ugent.sel.studeez.screens.timer_overview -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable @@ -16,6 +15,7 @@ import be.ugent.sel.studeez.common.composable.drawer.DrawerActions import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo +import be.ugent.sel.studeez.navigation.StudeezDestinations import be.ugent.sel.studeez.resources import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @@ -24,25 +24,29 @@ data class TimerOverviewActions( val getUserTimers: () -> Flow>, val getDefaultTimers: () -> List, val onEditClick: (TimerInfo) -> Unit, + val open: (String) -> Unit, ) fun getTimerOverviewActions( viewModel: TimerOverviewViewModel, + open: (String) -> Unit, ): TimerOverviewActions { return TimerOverviewActions( getUserTimers = viewModel::getUserTimers, getDefaultTimers = viewModel::getDefaultTimers, onEditClick = { viewModel.update(it) }, + open = open ) } @Composable fun TimerOverviewRoute( + open: (String) -> Unit, viewModel: TimerOverviewViewModel, drawerActions: DrawerActions, ) { TimerOverviewScreen( - timerOverviewActions = getTimerOverviewActions(viewModel), + timerOverviewActions = getTimerOverviewActions(viewModel, open), drawerActions = drawerActions, ) } @@ -59,36 +63,35 @@ fun TimerOverviewScreen( title = resources().getString(R.string.timers), drawerActions = drawerActions ) { - Column { - 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) {} - } - // User timers, can be edited - items(timers.value) { timerInfo -> - TimerEntry( - timerInfo = timerInfo, - ) { - StealthButton( - text = R.string.edit, - onClick = { timerOverviewActions.onEditClick(timerInfo) } - ) - } - - } + 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 + )) } - BasicButton(R.string.add_timer, Modifier.basicButton()) { - // TODO + // Default Timers, cannot be edited + items(timerOverviewActions.getDefaultTimers()) { + TimerEntry(timerInfo = it) {} + } + // User timers, can be edited + items(timers.value) { timerInfo -> + TimerEntry( + timerInfo = timerInfo, + ) { + StealthButton( + text = R.string.edit, + onClick = { timerOverviewActions.onEditClick(timerInfo) } + ) + } + + } + item { + BasicButton(R.string.add_timer, Modifier.basicButton()) { + timerOverviewActions.open(StudeezDestinations.ADD_TIMER_SCREEN) + } } } } @@ -104,7 +107,9 @@ fun TimerOverviewPreview() { timerOverviewActions = TimerOverviewActions( { flowOf() }, { listOf(customTimer, customTimer) }, - {}), + {}, + {} + ), drawerActions = DrawerActions({}, {}, {}, {}, {}) ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt new file mode 100644 index 0000000..fcfa79a --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt @@ -0,0 +1,12 @@ +package be.ugent.sel.studeez.screens.timer_overview.add_timer + +data class AddTimerUiState( + val studyTimeHours: Int = 1, + val studyTimeMinutes: Int = 0, + val withBreaks: Boolean = false, + val breakTimeMinutes: Int = 5, + val breakTimeHours: Int = 0, + val repeats: Int = 1, + val name: String = "Timer", + val description: String = "Long study session", +) \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt new file mode 100644 index 0000000..d507974 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt @@ -0,0 +1,91 @@ +package be.ugent.sel.studeez.screens.timer_overview.add_timer + +import androidx.compose.runtime.mutableStateOf +import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo +import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo +import be.ugent.sel.studeez.domain.LogService +import be.ugent.sel.studeez.domain.TimerDAO +import be.ugent.sel.studeez.screens.StudeezViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class AddTimerViewModel @Inject constructor( + logService: LogService, + private val timerDAO: TimerDAO, +): StudeezViewModel(logService) { + var uiState = mutableStateOf(AddTimerUiState()) + private set + + private val studyTimeHours + get() = uiState.value.studyTimeHours + + private val studyTimeMinutes + get() = uiState.value.studyTimeMinutes + + private val breakTimeHours + get() = uiState.value.breakTimeHours + + private val breakTimeMinutes + get() = uiState.value.breakTimeMinutes + + private val repeats + get() = uiState.value.repeats + + private val name + get() = uiState.value.name + + private val description + get() = uiState.value.description + + fun onStudyTimeHoursChange(newValue: Int) { + uiState.value = uiState.value.copy(studyTimeHours = newValue) + + } + + fun onStudyTimeMinutesChange(newValue: Int) { + uiState.value = uiState.value.copy(studyTimeMinutes = newValue) + } + + fun onWithBreaksChange() { + uiState.value = uiState.value.copy(withBreaks = !uiState.value.withBreaks) + } + + fun onBreakTimeHourChange(newValue: Int) { + uiState.value = uiState.value.copy(breakTimeHours = newValue) + } + + fun onBreakTimeMinutesChange(newValue: Int) { + uiState.value = uiState.value.copy(breakTimeMinutes = newValue) + } + + fun onRepeatsChange(newValue: Int) { + uiState.value = uiState.value.copy(repeats = newValue) + } + + fun addTimer() { + if (uiState.value.withBreaks) { + timerDAO.saveTimer(PomodoroTimerInfo( + name = uiState.value.name, + description = uiState.value.description, + studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60, + breakTime = breakTimeHours * 60 * 60 + breakTimeMinutes * 60, + repeats = repeats + )) + } else { + timerDAO.saveTimer(CustomTimerInfo( + name = uiState.value.name, + description = uiState.value.description, + studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60 + )) + } + } + + fun onNameChange(newValue: String) { + uiState.value = uiState.value.copy(name = newValue) + } + + fun onDescriptionChange(newValue: String) { + uiState.value = uiState.value.copy(description = newValue) + } +} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt new file mode 100644 index 0000000..77bdeda --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt @@ -0,0 +1,274 @@ +package be.ugent.sel.studeez.screens.timer_overview.add_timer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Button +import androidx.compose.material.Checkbox +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import be.ugent.sel.studeez.R +import be.ugent.sel.studeez.common.composable.BasicButton +import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate +import be.ugent.sel.studeez.common.composable.navbar.BasicTimePicker +import be.ugent.sel.studeez.navigation.StudeezDestinations +import be.ugent.sel.studeez.resources +import be.ugent.sel.studeez.ui.theme.StudeezTheme + +data class AddTimerActions( + val open: (String) -> Unit, + val goBack: () -> Unit, + val onStudyTimeHoursChange: (Int) -> Unit, + val onStudyTimeMinutesChange: (Int) -> Unit, + val onBreakTimeHourChange: (Int) -> Unit, + val onBreakTimeMinutesChange: (Int) -> Unit, + val onRepeatsChange: (Int) -> Unit, + val onWithBreaksChange: () -> Unit, + val addTimer: () -> Unit, + val onNameChange: (String) -> Unit, + val onDescriptionChange: (String) -> Unit, +) + +fun getAddTimerActions( + open: (String) -> Unit, + goBack: () -> Unit, + viewModel: AddTimerViewModel, +): AddTimerActions { + return AddTimerActions( + open = open, + goBack = goBack, + onWithBreaksChange = viewModel::onWithBreaksChange, + onStudyTimeHoursChange = viewModel::onStudyTimeHoursChange, + onStudyTimeMinutesChange = viewModel::onStudyTimeMinutesChange, + onBreakTimeHourChange = viewModel::onBreakTimeHourChange, + onBreakTimeMinutesChange = viewModel::onBreakTimeMinutesChange, + onRepeatsChange = viewModel::onRepeatsChange, + addTimer = viewModel::addTimer, + onNameChange = viewModel::onNameChange, + onDescriptionChange = viewModel::onDescriptionChange + ) +} + +@Composable +fun AddTimerRoute( + open: (String) -> Unit, + goBack: () -> Unit, + viewModel: AddTimerViewModel, +) { + val uiState by viewModel.uiState + + AddTimerScreen( + addTimerActions = getAddTimerActions( + open = open, + goBack = goBack, + viewModel = viewModel, + ), + uiState = uiState + ) +} + +@Composable +fun AddTimerScreen( + addTimerActions: AddTimerActions, + uiState: AddTimerUiState, +) { + val mStudyTimePicker = BasicTimePicker( + onHoursChange = addTimerActions.onStudyTimeHoursChange, + onMinutesChange = addTimerActions.onStudyTimeMinutesChange, + Hours = uiState.studyTimeHours, + Minutes = uiState.studyTimeMinutes + ) + + val mBreakTimePicker = BasicTimePicker( + onHoursChange = addTimerActions.onBreakTimeHourChange, + onMinutesChange = addTimerActions.onBreakTimeMinutesChange, + Hours = uiState.breakTimeHours, + Minutes = uiState.breakTimeMinutes + ) + + SecondaryScreenTemplate( + title = resources().getString(R.string.add_timer), + popUp = addTimerActions.goBack + ) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + Row( + modifier = Modifier + .padding(16.dp) + ) { + Text( + text = stringResource(R.string.addTimer_question), + textAlign = TextAlign.Center + ) + } + } + + item { + Text( + text = uiState.studyTimeHours.toString() + stringResource(R.string.addTimer_studytime_1) + uiState.studyTimeMinutes + stringResource( + R.string.addTimer_studytime_2) + ) + } + + item { + Button( + onClick = { + mStudyTimePicker.show() + }, + ) { + Text( + text = stringResource(R.string.addTimer_timepicker), + ) + } + } + + item { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.addTimer_break_question), + ) + Checkbox( + checked = uiState.withBreaks, + onCheckedChange = { addTimerActions.onWithBreaksChange() } + ) + } + } + + if (uiState.withBreaks) { + item { + Text( + text = if (uiState.repeats == 1) uiState.repeats.toString() + stringResource( + R.string.addTimer_break_1) + else uiState.repeats.toString() + stringResource( + R.string.addTimer_break_s) + ) + TextField( + value = uiState.repeats.toString(), + onValueChange = { it: String -> + it.toIntOrNull()?.let { it1 -> + addTimerActions.onRepeatsChange( + kotlin.math.abs(it1) + ) + } + } + ) + } + + item { + Text( + text = uiState.breakTimeHours.toString() + stringResource(R.string.breakTime_1) + uiState.breakTimeMinutes + stringResource( + R.string.breakTime_2) + ) + } + + item { + Button( + onClick = { + mBreakTimePicker.show() + }, + ) { + Text( + text = stringResource(R.string.addTimer_timepicker), + ) + } + } + } + + item { + Text( + text = stringResource(R.string.addTimer_name) + ) + } + + item { + TextField( + value = uiState.name, + onValueChange = { addTimerActions.onNameChange(it) } + ) + } + + item { + if (uiState.name == "") { + Text( + text = stringResource(R.string.addTimer_name_error), + color = Color.Red + ) + } + } + + item { + Text( + text = stringResource(R.string.addTimer_description) + ) + } + + item { + TextField( + value = uiState.description, + onValueChange = { addTimerActions.onDescriptionChange(it) } + ) + } + + item { + if (uiState.description == "") { + Text( + text = stringResource(R.string.addTimer_description_error), + color = Color.Red + ) + } + } + + item { + Row( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(), + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.Center + ) { + BasicButton( + text = R.string.add_timer, + modifier = Modifier, + onClick = { + if (uiState.description != "" && uiState.name != "") { + addTimerActions.addTimer() + addTimerActions.open(StudeezDestinations.TIMER_OVERVIEW_SCREEN) + } + } + ) + } + } + } + } +} + +@Preview +@Composable +fun AddTimerScreenPreview() { StudeezTheme { + AddTimerScreen( + addTimerActions = AddTimerActions({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}), + uiState = AddTimerUiState() + ) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1453608..8f02fc5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,5 +107,20 @@ Creating sessions still needs to be implemented. Hang on tight! + + + Timer description cannot be empty! + Timer description + Timer name cannot be empty! + Timer name + Open Time Picker + " hours and " + " minutes of breaktime" + " break" + " breaks" + With breaks? + " hours and " + " minutes of studytime" + How long do you want to study? diff --git a/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt b/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt index 60740d9..f9e34c3 100644 --- a/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt +++ b/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt @@ -2,6 +2,7 @@ package be.ugent.sel.studeez.timer_functional import android.media.MediaPlayer import be.ugent.sel.studeez.data.SelectedTimerState +import be.ugent.sel.studeez.data.SessionReportState import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer @@ -24,7 +25,7 @@ class InvisibleSessionManagerTest { @Test fun InvisibleEndlessTimerTest() = runTest { timerState.selectedTimer = FunctionalEndlessTimer() - viewModel = SessionViewModel(timerState, mock()) + viewModel = SessionViewModel(timerState, SessionReportState(), mock()) InvisibleSessionManager.setParameters(viewModel, mediaPlayer) val test = launch { @@ -46,7 +47,7 @@ class InvisibleSessionManagerTest { val breakTime = 5 val repeats = 1 timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats) - viewModel = SessionViewModel(timerState, mock()) + viewModel = SessionViewModel(timerState, SessionReportState(), mock()) InvisibleSessionManager.setParameters(viewModel, mediaPlayer) val test = launch { @@ -79,7 +80,7 @@ class InvisibleSessionManagerTest { @Test fun InvisibleCustomTimerTest() = runTest { timerState.selectedTimer = FunctionalCustomTimer(5) - viewModel = SessionViewModel(timerState, mock()) + viewModel = SessionViewModel(timerState, SessionReportState(), mock()) InvisibleSessionManager.setParameters(viewModel, mediaPlayer) val test = launch {