Add AlarmRow composable

pull/1952/head
Alex Baker 3 years ago
parent 00c80337de
commit 1cac090c9d

@ -231,6 +231,7 @@ dependencies {
implementation("com.google.android.material:compose-theme-adapter:${Versions.compose_theme_adapter}") implementation("com.google.android.material:compose-theme-adapter:${Versions.compose_theme_adapter}")
implementation("androidx.activity:activity-compose:1.4.0") implementation("androidx.activity:activity-compose:1.4.0")
implementation("androidx.compose.material:material-icons-extended:${Versions.compose}") implementation("androidx.compose.material:material-icons-extended:${Versions.compose}")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1")
releaseCompileOnly("androidx.compose.ui:ui-tooling:${Versions.compose}") releaseCompileOnly("androidx.compose.ui:ui-tooling:${Versions.compose}")
implementation("com.google.accompanist:accompanist-flowlayout:${Versions.accompanist}") implementation("com.google.accompanist:accompanist-flowlayout:${Versions.accompanist}")

@ -1,37 +1,27 @@
package com.todoroo.astrid.ui package com.todoroo.astrid.ui
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.compose.foundation.clickable import android.view.View
import androidx.compose.foundation.interaction.MutableInteractionSource import android.view.ViewGroup
import androidx.compose.foundation.layout.* import androidx.compose.runtime.getValue
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.AndroidUtilities.atLeastTiramisu
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R import org.tasks.R
import org.tasks.activities.DateAndTimePickerActivity import org.tasks.activities.DateAndTimePickerActivity
import org.tasks.compose.AddReminderDialog
import org.tasks.compose.AlarmRow
import org.tasks.compose.DisabledText
import org.tasks.compose.collectAsStateLifecycleAware import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.edit.AlarmRow
import org.tasks.data.Alarm import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
import org.tasks.data.Alarm.Companion.TYPE_REL_END import org.tasks.data.Alarm.Companion.TYPE_REL_END
@ -42,8 +32,8 @@ import org.tasks.data.Alarm.Companion.whenStarted
import org.tasks.date.DateTimeUtils import org.tasks.date.DateTimeUtils
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.MyTimePickerDialog import org.tasks.dialogs.MyTimePickerDialog
import org.tasks.reminders.AlarmToString
import org.tasks.ui.TaskEditControlFragment import org.tasks.ui.TaskEditControlFragment
import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -51,7 +41,7 @@ import javax.inject.Inject
class ReminderControlSet : TaskEditControlFragment() { class ReminderControlSet : TaskEditControlFragment() {
@Inject lateinit var activity: Activity @Inject lateinit var activity: Activity
@Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var alarmToString: AlarmToString @Inject lateinit var locale: Locale
data class ViewState( data class ViewState(
val showCustomDialog: Boolean = false, val showCustomDialog: Boolean = false,
@ -92,7 +82,7 @@ class ReminderControlSet : TaskEditControlFragment() {
} }
private fun addAlarm(selected: String) { private fun addAlarm(selected: String) {
val id = viewModel.task?.id ?: 0 val id = viewModel.task.id
when (selected) { when (selected) {
getString(R.string.when_started) -> getString(R.string.when_started) ->
addAlarmRow(whenStarted(id)) addAlarmRow(whenStarted(id))
@ -124,112 +114,38 @@ class ReminderControlSet : TaskEditControlFragment() {
} }
} }
@OptIn(ExperimentalComposeUiApi::class, ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
@Composable override fun bind(parent: ViewGroup?): View =
override fun Body() { (parent as ComposeView).apply {
val viewState = vm.viewState.collectAsStateLifecycleAware() setContent {
val current: ViewState = viewState.value MdcTheme {
val notificationPermissions = if (atLeastTiramisu()) { val ringMode by remember { this@ReminderControlSet.ringMode }
rememberPermissionState( val notificationPermissions = if (AndroidUtilities.atLeastTiramisu()) {
android.Manifest.permission.POST_NOTIFICATIONS rememberPermissionState(
) Manifest.permission.POST_NOTIFICATIONS
} else {
null
}
when (notificationPermissions?.status ?: PermissionStatus.Granted) {
PermissionStatus.Granted ->
Alarms()
is PermissionStatus.Denied -> {
Column(
modifier = Modifier.clickable {
notificationPermissions?.launchPermissionRequest()
}
) {
Spacer(modifier = Modifier.height(20.dp))
Text(
text = stringResource(id = R.string.enable_reminders),
color = colorResource(id = R.color.red_500),
)
Text(
text = stringResource(id = R.string.enable_reminders_description),
style = MaterialTheme.typography.caption,
color = colorResource(id = R.color.red_500),
)
Spacer(modifier = Modifier.height(20.dp))
}
}
}
AddReminderDialog.AddCustomReminderDialog(
openDialog = current.showCustomDialog,
addAlarm = this::addAlarmRow,
closeDialog = {
vm.showCustomDialog(visible = false)
AndroidUtilities.hideKeyboard(activity)
}
)
AddReminderDialog.AddRandomReminderDialog(
openDialog = current.showRandomDialog,
addAlarm = this::addAlarmRow,
closeDialog = {
vm.showRandomDialog(visible = false)
AndroidUtilities.hideKeyboard(activity)
}
)
}
@Composable
fun Alarms() {
Column {
val alarms = viewModel.selectedAlarms.collectAsStateLifecycleAware()
Spacer(modifier = Modifier.height(8.dp))
alarms.value.forEach { alarm ->
AlarmRow(alarmToString.toString(alarm)) {
viewModel.selectedAlarms.value =
viewModel.selectedAlarms.value.minus(alarm)
}
}
Row(modifier = Modifier.fillMaxWidth()) {
DisabledText(
text = stringResource(id = R.string.add_reminder),
modifier = Modifier
.padding(vertical = 12.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
onClick = { addAlarm() }
) )
) } else {
Spacer(modifier = Modifier.weight(1f)) null
val ringMode = remember { this@ReminderControlSet.ringMode } }
if (alarms.value.isNotEmpty()) { AlarmRow(
Text( locale = locale,
text = stringResource( alarms = viewModel.selectedAlarms.collectAsStateLifecycleAware().value,
id = when (ringMode.value) { permissionStatus = notificationPermissions?.status
2 -> R.string.ring_nonstop ?: PermissionStatus.Granted,
1 -> R.string.ring_five_times launchPermissionRequest = {
else -> R.string.ring_once notificationPermissions?.launchPermissionRequest()
} },
), ringMode = ringMode,
style = MaterialTheme.typography.body1.copy( newAlarm = this@ReminderControlSet::addAlarm,
textDecoration = TextDecoration.Underline addAlarm = this@ReminderControlSet::addAlarmRow,
), openRingType = this@ReminderControlSet::onClickRingType,
modifier = Modifier deleteAlarm = {
.padding(vertical = 12.dp, horizontal = 16.dp) viewModel.selectedAlarms.value = viewModel.selectedAlarms.value.minus(it)
.clickable( }
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
onClick = { onClickRingType() }
)
) )
} }
} }
Spacer(modifier = Modifier.height(8.dp))
} }
}
override val icon = R.drawable.ic_outline_notifications_24px
override fun controlId() = TAG override fun controlId() = TAG
@ -284,4 +200,3 @@ class ReminderControlSet : TaskEditControlFragment() {
private const val REQUEST_NEW_ALARM = 12152 private const val REQUEST_NEW_ALARM = 12152
} }
} }

@ -0,0 +1,196 @@
package org.tasks.compose.edit
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.astrid.ui.ReminderControlSet
import com.todoroo.astrid.ui.ReminderControlSetViewModel
import org.tasks.R
import org.tasks.compose.AddReminderDialog
import org.tasks.compose.DisabledText
import org.tasks.compose.TaskEditRow
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.data.Alarm
import org.tasks.reminders.AlarmToString
import java.util.*
@OptIn(ExperimentalPermissionsApi::class, ExperimentalComposeUiApi::class)
@Composable
fun AlarmRow(
vm: ReminderControlSetViewModel = viewModel(),
permissionStatus: PermissionStatus,
launchPermissionRequest: () -> Unit,
alarms: List<Alarm>,
ringMode: Int,
locale: Locale,
newAlarm: () -> Unit,
addAlarm: (Alarm) -> Unit,
deleteAlarm: (Alarm) -> Unit,
openRingType: () -> Unit,
) {
TaskEditRow(
iconRes = R.drawable.ic_outline_notifications_24px,
content = {
val viewState = vm.viewState.collectAsStateLifecycleAware()
val current: ReminderControlSet.ViewState = viewState.value
when (permissionStatus) {
PermissionStatus.Granted -> {
Alarms(
alarms = alarms,
ringMode = ringMode,
locale = locale,
addAlarm = newAlarm,
deleteAlarm = deleteAlarm,
openRingType = openRingType,
)
}
is PermissionStatus.Denied -> {
Column(
modifier = Modifier
.padding(end = 16.dp)
.clickable {
launchPermissionRequest()
}
) {
Spacer(modifier = Modifier.height(20.dp))
Text(
text = stringResource(id = R.string.enable_reminders),
color = colorResource(id = R.color.red_500),
)
Text(
text = stringResource(id = R.string.enable_reminders_description),
style = MaterialTheme.typography.caption,
color = colorResource(id = R.color.red_500),
)
Spacer(modifier = Modifier.height(20.dp))
}
}
}
AddReminderDialog.AddCustomReminderDialog(
openDialog = current.showCustomDialog,
addAlarm = addAlarm,
closeDialog = { vm.showCustomDialog(visible = false) }
)
AddReminderDialog.AddRandomReminderDialog(
openDialog = current.showRandomDialog,
addAlarm = addAlarm,
closeDialog = { vm.showRandomDialog(visible = false) }
)
},
)
}
@Composable
fun Alarms(
alarms: List<Alarm>,
ringMode: Int,
locale: Locale,
addAlarm: () -> Unit,
deleteAlarm: (Alarm) -> Unit,
openRingType: () -> Unit,
) {
Column {
Spacer(modifier = Modifier.height(8.dp))
alarms.forEach { alarm ->
org.tasks.compose.AlarmRow(AlarmToString(LocalContext.current, locale).toString(alarm)) {
deleteAlarm(alarm)
}
}
Row(modifier = Modifier.fillMaxWidth()) {
DisabledText(
text = stringResource(id = R.string.add_reminder),
modifier = Modifier
.padding(vertical = 12.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
onClick = { addAlarm() }
)
)
Spacer(modifier = Modifier.weight(1f))
if (alarms.isNotEmpty()) {
Text(
text = stringResource(
id = when (ringMode) {
2 -> R.string.ring_nonstop
1 -> R.string.ring_five_times
else -> R.string.ring_once
}
),
style = MaterialTheme.typography.body1.copy(
textDecoration = TextDecoration.Underline
),
modifier = Modifier
.padding(vertical = 12.dp, horizontal = 16.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
onClick = openRingType
)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Preview(showBackground = true, widthDp = 320)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320)
@Composable
fun NoAlarms() {
MdcTheme {
AlarmRow(
alarms = emptyList(),
ringMode = 0,
locale = Locale.getDefault(),
newAlarm = {},
addAlarm = {},
deleteAlarm = {},
openRingType = {},
permissionStatus = PermissionStatus.Granted,
launchPermissionRequest = {}
)
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Preview(showBackground = true, widthDp = 320)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320)
@Composable
fun PermissionDenied() {
MdcTheme {
AlarmRow(
alarms = emptyList(),
ringMode = 0,
locale = Locale.getDefault(),
newAlarm = {},
addAlarm = {},
deleteAlarm = {},
openRingType = {},
permissionStatus = PermissionStatus.Denied(true),
launchPermissionRequest = {}
)
}
}

@ -3,18 +3,13 @@ package org.tasks.reminders
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R import org.tasks.R
import org.tasks.data.Alarm import org.tasks.data.Alarm
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
class AlarmToString @Inject constructor( class AlarmToString constructor(context: Context, var locale: Locale) {
@ApplicationContext context: Context,
var locale: Locale,
) {
private val resources = context.resources private val resources = context.resources
fun toString(alarm: Alarm): String { fun toString(alarm: Alarm): String {

@ -567,6 +567,11 @@
+| +--- androidx.compose.material:material-icons-core:1.2.0-rc02 (*) +| +--- androidx.compose.material:material-icons-core:1.2.0-rc02 (*)
+| +--- androidx.compose.runtime:runtime:1.2.0-rc02 (*) +| +--- androidx.compose.runtime:runtime:1.2.0-rc02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21 +| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21
++--- androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1
+| +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0 -> 2.5.0-rc01 (*)
+| +--- androidx.compose.runtime:runtime:1.0.1 -> 1.2.0-rc02 (*)
+| +--- androidx.compose.ui:ui:1.0.1 -> 1.2.0-rc02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.30 -> 1.6.21 (*)
++--- com.google.accompanist:accompanist-flowlayout:0.24.12-rc ++--- com.google.accompanist:accompanist-flowlayout:0.24.12-rc
+| +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*) +| +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*)
+| \--- androidx.compose.ui:ui-util:1.2.0-rc02 (*) +| \--- androidx.compose.ui:ui-util:1.2.0-rc02 (*)

@ -703,6 +703,11 @@
+| +--- androidx.compose.material:material-icons-core:1.2.0-rc02 (*) +| +--- androidx.compose.material:material-icons-core:1.2.0-rc02 (*)
+| +--- androidx.compose.runtime:runtime:1.2.0-rc02 (*) +| +--- androidx.compose.runtime:runtime:1.2.0-rc02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21 +| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21
++--- androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1
+| +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0 -> 2.5.0-rc01 (*)
+| +--- androidx.compose.runtime:runtime:1.0.1 -> 1.2.0-rc02 (*)
+| +--- androidx.compose.ui:ui:1.0.1 -> 1.2.0-rc02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.30 -> 1.6.21 (*)
++--- com.google.accompanist:accompanist-flowlayout:0.24.12-rc ++--- com.google.accompanist:accompanist-flowlayout:0.24.12-rc
+| +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*) +| +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*)
+| \--- androidx.compose.ui:ui-util:1.2.0-rc02 (*) +| \--- androidx.compose.ui:ui-util:1.2.0-rc02 (*)

Loading…
Cancel
Save