@@ -24,14 +24,49 @@ package net.opatry.countdowntimer
2424import android.os.Bundle
2525import androidx.activity.compose.setContent
2626import androidx.appcompat.app.AppCompatActivity
27+ import androidx.compose.foundation.ExperimentalFoundationApi
28+ import androidx.compose.foundation.layout.Arrangement
29+ import androidx.compose.foundation.layout.Column
30+ import androidx.compose.foundation.layout.fillMaxHeight
31+ import androidx.compose.foundation.layout.fillMaxWidth
32+ import androidx.compose.foundation.layout.padding
33+ import androidx.compose.material.BackdropScaffold
34+ import androidx.compose.material.ExperimentalMaterialApi
35+ import androidx.compose.material.FloatingActionButton
36+ import androidx.compose.material.Icon
2737import androidx.compose.material.MaterialTheme
2838import androidx.compose.material.Surface
2939import androidx.compose.material.Text
40+ import androidx.compose.material.icons.Icons
41+ import androidx.compose.material.icons.twotone.PlayArrow
3042import androidx.compose.runtime.Composable
31- import androidx.compose.ui.tooling.preview.Preview
43+ import androidx.compose.runtime.LaunchedEffect
44+ import androidx.compose.runtime.getValue
45+ import androidx.compose.runtime.livedata.observeAsState
46+ import androidx.compose.runtime.mutableStateOf
47+ import androidx.compose.runtime.remember
48+ import androidx.compose.runtime.setValue
49+ import androidx.compose.ui.Alignment
50+ import androidx.compose.ui.Modifier
51+ import androidx.compose.ui.res.stringResource
52+ import androidx.compose.ui.unit.dp
53+ import androidx.lifecycle.viewmodel.compose.viewModel
54+ import kotlinx.coroutines.delay
55+ import kotlinx.coroutines.isActive
56+ import net.opatry.countdowntimer.ui.component.TimerCircle
57+ import net.opatry.countdowntimer.ui.component.TimerControls
58+ import net.opatry.countdowntimer.ui.component.TimerLabel
59+ import net.opatry.countdowntimer.ui.component.TimerList
3260import net.opatry.countdowntimer.ui.theme.MyTheme
61+ import kotlin.math.roundToInt
62+ import kotlin.time.ExperimentalTime
63+ import kotlin.time.milliseconds
3364
3465class MainActivity : AppCompatActivity () {
66+ @ExperimentalTime
67+ @ExperimentalMaterialApi
68+ @ExperimentalStdlibApi
69+ @ExperimentalFoundationApi
3570 override fun onCreate (savedInstanceState : Bundle ? ) {
3671 super .onCreate(savedInstanceState)
3772 setContent {
@@ -44,8 +79,126 @@ class MainActivity : AppCompatActivity() {
4479
4580// Start building your app here!
4681@Composable
82+ @ExperimentalTime
83+ @ExperimentalStdlibApi
84+ @ExperimentalMaterialApi
85+ @ExperimentalFoundationApi
4786fun MyApp () {
4887 Surface (color = MaterialTheme .colors.background) {
49- Text (text = " Ready... Set... GO! " )
88+ CountDownTimerDispatcher ( )
5089 }
5190}
91+
92+ @Composable
93+ @ExperimentalTime
94+ @ExperimentalStdlibApi
95+ @ExperimentalMaterialApi
96+ @ExperimentalFoundationApi
97+ fun CountDownTimerDispatcher () {
98+ val viewModel = viewModel<CounterViewModel >()
99+ val timers by viewModel.timers.observeAsState(listOf ())
100+ val state by viewModel.state.observeAsState(TimerState .Reset (null ))
101+
102+ state.let { uiState ->
103+ when (uiState) {
104+ is TimerState .Reset -> CountDownTimerReset {
105+ // TODO TODO handle no timer and ask for a duration & name
106+ viewModel.start(timers[0 ])
107+ }
108+ is TimerState .Running -> {
109+ val activeTimer = uiState.timer
110+ CountDownTimerLayout (
111+ activeTimer,
112+ timers,
113+ onTimerClicked = {
114+ viewModel.start(it)
115+ },
116+ onFABClicked = {
117+ viewModel.reset()
118+ // viewModel.pause()
119+ }
120+ )
121+ }
122+ // is TimerState.Paused -> CountDownTimerLayout(
123+ // onTimerClicked = {}
124+ // ) {
125+ // viewModel.resume()
126+ // }
127+ // is TimerState.Done -> CountDownTimerLayout(
128+ // onTimerClicked = {}
129+ // ) {
130+ // viewModel.reset()
131+ // }
132+ }
133+ }
134+ }
135+
136+ @Composable
137+ fun CountDownTimerReset (onFABClicked : () -> Unit ) {
138+ Column {
139+ Text (" TODO CREATE NEW TIMER" , Modifier .padding(48 .dp))
140+ FloatingActionButton (onClick = onFABClicked) {
141+ Icon (Icons .TwoTone .PlayArrow , stringResource(R .string.timer_start))
142+ }
143+ }
144+ }
145+
146+ @Composable
147+ @ExperimentalTime
148+ @ExperimentalMaterialApi
149+ @ExperimentalFoundationApi
150+ fun CountDownTimerLayout (
151+ activeTimer : Timer ,
152+ timers : List <Timer >,
153+ onTimerClicked : (Timer ) -> Unit ,
154+ onFABClicked : () -> Unit
155+ ) {
156+ // FIXME there is a bug when finishing the first progress for the first time, progress sticks to max
157+ var remainingDuration by remember/* Saveable TODO Duration to bundle*/ { mutableStateOf(activeTimer.duration) }
158+ LaunchedEffect (activeTimer/* .name*/ ) {
159+ // FIXME how to smoothly animate the progress and update remaining time accordingly
160+ // FIXME not stopped even when key change :(
161+ while (! remainingDuration.isNegative() && isActive) {
162+ remainingDuration = remainingDuration - 16 .milliseconds
163+ delay(16 )
164+ }
165+ }
166+
167+ val hours = (remainingDuration.inHours % 12 ).coerceAtLeast(.0 )
168+ val minutes = (remainingDuration.inMinutes % 60 ).coerceAtLeast(.0 )
169+ val seconds = (remainingDuration.inSeconds % 60 ).coerceAtLeast(.0 )
170+
171+ BackdropScaffold (
172+ appBar = { },
173+ backLayerContent = {
174+ Column (
175+ Modifier .fillMaxWidth()
176+ ) {
177+ TimerList (activeTimer, timers, onTimerClicked)
178+ }
179+ },
180+ frontLayerContent = {
181+ Column (
182+ Modifier
183+ .fillMaxWidth()
184+ .fillMaxHeight(.8f ),
185+ horizontalAlignment = Alignment .CenterHorizontally ,
186+ verticalArrangement = Arrangement .SpaceEvenly
187+ ) {
188+ // FIXME values rounding is incorrect
189+ // 1. when switching from 35min 30sec to 35min 29sec, it displays 34min 29sec
190+ // 2. when seconds is almost 0 but not yet, digit changes to 0 before the progress reach 12O'clock
191+ // (especially visible for last second)
192+ val hoursI = hours.roundToInt()
193+ val minutesI = minutes.roundToInt()
194+ val secondsI = seconds.roundToInt()
195+ TimerLabel (hoursI, minutesI, secondsI)
196+ val hoursP = if (hoursI == 0 ) 0f else (hours / 12 ).toFloat()
197+ val minutesP = if (minutesI == 0 ) 0f else (minutes / 60 ).toFloat()
198+ val secondsP = if (secondsI == 0 ) 0f else (seconds / 60 ).toFloat()
199+ TimerCircle (hoursP, minutesP, secondsP, onFABClicked)
200+ TimerControls (onClose = {}, onDelete = {})
201+ }
202+ }
203+ )
204+ }
0 commit comments