From c1b0c49cc216114f0fb2e74d57b0c021ee47f78d Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 27 Feb 2022 19:24:22 +0100 Subject: [PATCH] Add alarm part --- .../ui/launcher/widgets/clock/ClockWidget.kt | 18 +++- .../launcher/widgets/clock/ClockWidgetVM.kt | 28 +++-- .../widgets/clock/parts/AlarmPartProvider.kt | 101 ++++++++++++++++++ .../widgets/clock/parts/DatePartProvider.kt | 13 ++- .../widgets/clock/parts/MusicPartProvider.kt | 6 +- .../widgets/clock/parts/PartProvider.kt | 5 +- 6 files changed, 156 insertions(+), 15 deletions(-) create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/AlarmPartProvider.kt diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt index 3a19803e..b2b3bdf9 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt @@ -14,7 +14,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockStyle import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout @@ -28,11 +31,18 @@ fun ClockWidget( ) { val viewModel: ClockWidgetVM = viewModel() val context = LocalContext.current - val time by viewModel.getTime(context).collectAsState(System.currentTimeMillis()) + val lifecycleOwner = LocalLifecycleOwner.current + val time by viewModel.time.collectAsState(System.currentTimeMillis()) val layout by viewModel.layout.observeAsState() val clockStyle by viewModel.clockStyle.observeAsState() - val partProvider by viewModel.getActivePart().collectAsState(null) + LaunchedEffect(null) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.onActive(context) + } + } + + val partProvider by viewModel.getActivePart(LocalContext.current).collectAsState(null) Box( modifier = Modifier @@ -68,7 +78,9 @@ fun ClockWidget( } if (layout == ClockWidgetLayout.Horizontal) { Column( - modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Icon(imageVector = Icons.Rounded.ExpandLess, contentDescription = "") diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt index 839fc660..5e37970a 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt @@ -10,13 +10,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.ui.launcher.widgets.clock.parts.AlarmPartProvider import de.mm20.launcher2.ui.launcher.widgets.clock.parts.DatePartProvider import de.mm20.launcher2.ui.launcher.widgets.clock.parts.MusicPartProvider import de.mm20.launcher2.ui.launcher.widgets.clock.parts.PartProvider import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -26,16 +26,22 @@ class ClockWidgetVM : ViewModel(), KoinComponent { private val partProviders = MutableStateFlow>(emptyList()) init { - partProviders.value = listOf(DatePartProvider(), MusicPartProvider()) + partProviders.value = listOf( + DatePartProvider(), + MusicPartProvider(), + AlarmPartProvider() + ) } - fun getActivePart(): Flow = channelFlow { + val time = MutableStateFlow(System.currentTimeMillis()) + + fun getActivePart(context: Context): Flow = channelFlow { partProviders.collectLatest { providers -> if (providers.isEmpty()) { send(null) return@collectLatest } - val rankings = providers.map { it.getRanking() } + val rankings = providers.map { it.getRanking(context) } combine(rankings) { r -> var prov = providers[0] for (i in 1 until providers.size) { @@ -43,7 +49,6 @@ class ClockWidgetVM : ViewModel(), KoinComponent { prov = providers[i] } } - Log.d("MM20", prov.toString()) return@combine prov }.collectLatest { send(it) @@ -55,7 +60,7 @@ class ClockWidgetVM : ViewModel(), KoinComponent { val layout = dataStore.data.map { it.clockWidget.layout }.asLiveData() val clockStyle = dataStore.data.map { it.clockWidget.clockStyle }.asLiveData() - fun getTime(context: Context): Flow = callbackFlow { + private fun getTime(context: Context): Flow = callbackFlow { trySendBlocking(System.currentTimeMillis()) val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -71,9 +76,20 @@ class ClockWidgetVM : ViewModel(), KoinComponent { } } + private fun updatePartsTime(time: Long) { + partProviders.value.forEach { it.setTime(time) } + } + fun launchClockApp(context: Context) { context.tryStartActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }) } + + suspend fun onActive(context: Context) { + getTime(context).collectLatest { + time.value = it + updatePartsTime(it) + } + } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/AlarmPartProvider.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/AlarmPartProvider.kt new file mode 100644 index 00000000..935bb0ea --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/AlarmPartProvider.kt @@ -0,0 +1,101 @@ +package de.mm20.launcher2.ui.launcher.widgets.clock.parts + +import android.app.AlarmManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.provider.AlarmClock +import android.text.format.DateUtils +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Alarm +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.core.content.getSystemService +import androidx.lifecycle.MutableLiveData +import de.mm20.launcher2.ktx.tryStartActivity +import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.* + +class AlarmPartProvider : PartProvider { + + private val nextAlarmTime = MutableLiveData(null) + + private val time = MutableStateFlow(System.currentTimeMillis()) + + override fun setTime(time: Long) { + this.time.value = time + } + + override fun getRanking(context: Context): Flow = channelFlow { + val nextAlarm = getNextAlarmTime(context) + nextAlarm.collectLatest { alarm -> + nextAlarmTime.value = alarm + if (alarm == null) { + send(0) + } else { + time.collectLatest { + if (alarm > it + 15 * 60 * 1000) { + send(0) + } else { + send(60) + } + } + } + } + } + + private fun getNextAlarmTime(context: Context): Flow = callbackFlow { + val alarmManager: AlarmManager = context.getSystemService() ?: return@callbackFlow + trySendBlocking(alarmManager.nextAlarmClock?.triggerTime) + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + trySendBlocking(alarmManager.nextAlarmClock?.triggerTime) + } + } + context.registerReceiver(receiver, IntentFilter().apply { + addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED) + }) + awaitClose { + context.unregisterReceiver(receiver) + } + } + + @Composable + override fun Component(layout: ClockWidgetLayout) { + val context = LocalContext.current + + val alarmTime by nextAlarmTime.observeAsState(null) + val time by this.time.collectAsState(System.currentTimeMillis()) + + alarmTime?.let { + TextButton(onClick = { + context.tryStartActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS)) + }, + colors = ButtonDefaults.textButtonColors( + contentColor = LocalContentColor.current + ) + ) { + Icon( + imageVector = Icons.Rounded.Alarm, + contentDescription = null + ) + Text( + modifier = Modifier.padding(start = 12.dp), + text = DateUtils.getRelativeTimeSpanString(it, time, DateUtils.MINUTE_IN_MILLIS) + .toString(), + style = MaterialTheme.typography.labelLarge + ) + } + } + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/DatePartProvider.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/DatePartProvider.kt index 2898352b..fae7a2e3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/DatePartProvider.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/DatePartProvider.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.ui.launcher.widgets.clock.parts import android.content.ContentUris +import android.content.Context import android.content.Intent import android.provider.CalendarContract import android.text.format.DateFormat @@ -20,19 +21,25 @@ import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidgetVM import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flow import java.text.SimpleDateFormat import java.util.* class DatePartProvider: PartProvider { - override fun getRanking(): Flow = flow { + override fun getRanking(context: Context): Flow = flow { emit(1) } + private val time = MutableStateFlow(System.currentTimeMillis()) + + override fun setTime(time: Long) { + this.time.value = time + } + @Composable override fun Component(layout: Settings.ClockWidgetSettings.ClockWidgetLayout) { - val viewModel: ClockWidgetVM = viewModel() - val time by viewModel.getTime(LocalContext.current).collectAsState(System.currentTimeMillis()) + val time by this.time.collectAsState(System.currentTimeMillis()) val verticalLayout = layout == Settings.ClockWidgetSettings.ClockWidgetLayout.Vertical val context = LocalContext.current TextButton(onClick = { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt index fc64bda2..f007ac22 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt @@ -1,10 +1,12 @@ package de.mm20.launcher2.ui.launcher.widgets.clock.parts import android.app.PendingIntent +import android.content.Context import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -39,7 +41,7 @@ class MusicPartProvider : PartProvider, KoinComponent { private val musicRepository: MusicRepository by inject() - override fun getRanking(): Flow = channelFlow { + override fun getRanking(context: Context): Flow = channelFlow { musicRepository.playbackState.collectLatest { if (it == PlaybackState.Stopped) send(0) else send(50) @@ -48,7 +50,7 @@ class MusicPartProvider : PartProvider, KoinComponent { @OptIn( ExperimentalAnimationGraphicsApi::class, - androidx.compose.foundation.ExperimentalFoundationApi::class + ExperimentalFoundationApi::class ) @Composable override fun Component(layout: ClockWidgetLayout) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/PartProvider.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/PartProvider.kt index fdd7e5b2..7c5c7e03 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/PartProvider.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/PartProvider.kt @@ -1,5 +1,6 @@ package de.mm20.launcher2.ui.launcher.widgets.clock.parts +import android.content.Context import androidx.compose.runtime.Composable import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout @@ -7,7 +8,9 @@ import kotlinx.coroutines.flow.Flow interface PartProvider { - fun getRanking(): Flow + fun getRanking(context: Context): Flow + + fun setTime(time: Long) {} @Composable fun Component(layout: ClockWidgetLayout)