diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2429feef4..d712bc27e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -233,7 +233,8 @@ dependencies {
implementation("androidx.compose.material:material-icons-extended:${Versions.compose}")
releaseCompileOnly("androidx.compose.ui:ui-tooling:${Versions.compose}")
- implementation("com.google.accompanist:accompanist-flowlayout:0.24.11-rc")
+ implementation("com.google.accompanist:accompanist-flowlayout:${Versions.accompanist}")
+ implementation("com.google.accompanist:accompanist-permissions:${Versions.accompanist}")
googleplayImplementation("com.google.firebase:firebase-crashlytics:${Versions.crashlytics}")
googleplayImplementation("com.google.firebase:firebase-analytics:${Versions.analytics}") {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 56e5bb397..98e97937d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,8 @@
+
+
diff --git a/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java b/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java
index b8958dbc2..e06473a9c 100644
--- a/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java
+++ b/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java
@@ -163,6 +163,10 @@ public class AndroidUtilities {
return VERSION.SDK_INT >= VERSION_CODES.S;
}
+ public static boolean atLeastTiramisu() {
+ return VERSION.SDK_INT >= VERSION_CODES.TIRAMISU;
+ }
+
public static void assertMainThread() {
if (BuildConfig.DEBUG && !isMainThread()) {
throw new IllegalStateException("Should be called from main thread");
diff --git a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt
index 346b27c58..f7dacb12b 100644
--- a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt
+++ b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt
@@ -17,11 +17,17 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
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.unit.dp
import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.viewModels
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.PermissionStatus
+import com.google.accompanist.permissions.rememberPermissionState
import com.todoroo.andlib.utility.AndroidUtilities
+import com.todoroo.andlib.utility.AndroidUtilities.atLeastTiramisu
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.activities.DateAndTimePickerActivity
@@ -49,13 +55,15 @@ class ReminderControlSet : TaskEditControlComposeFragment() {
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var alarmToString: AlarmToString
- private val showCustomDialog = mutableStateOf(false)
- private val showRandomDialog = mutableStateOf(false)
+ data class ViewState(
+ val showCustomDialog: Boolean = false,
+ val showRandomDialog: Boolean = false,
+ )
+
private val ringMode = mutableStateOf(0)
+ private val vm: ReminderControlSetViewModel by viewModels()
override fun createView(savedInstanceState: Bundle?) {
- showCustomDialog.value = savedInstanceState?.getBoolean(CUSTOM_DIALOG_VISIBLE) ?: false
- showRandomDialog.value = savedInstanceState?.getBoolean(RANDOM_DIALOG_VISIBLE) ?: false
when {
viewModel.ringNonstop!! -> setRingMode(2)
viewModel.ringFiveTimes!! -> setRingMode(1)
@@ -63,13 +71,6 @@ class ReminderControlSet : TaskEditControlComposeFragment() {
}
}
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
-
- outState.putBoolean(CUSTOM_DIALOG_VISIBLE, showCustomDialog.value)
- outState.putBoolean(RANDOM_DIALOG_VISIBLE, showRandomDialog.value)
- }
-
private fun onClickRingType() {
val modes = resources.getStringArray(R.array.reminder_ring_modes)
val ringMode = when {
@@ -102,11 +103,11 @@ class ReminderControlSet : TaskEditControlComposeFragment() {
getString(R.string.when_overdue) ->
addAlarmRow(whenOverdue(id))
getString(R.string.randomly) ->
- addRandomAlarm()
+ vm.showRandomDialog(visible = true)
getString(R.string.pick_a_date_and_time) ->
addNewAlarm()
getString(R.string.repeat_option_custom) ->
- addCustomAlarm()
+ vm.showCustomDialog(visible = true)
}
}
@@ -125,11 +126,65 @@ class ReminderControlSet : TaskEditControlComposeFragment() {
}
}
- @OptIn(ExperimentalComposeUiApi::class)
+ @OptIn(ExperimentalComposeUiApi::class, ExperimentalPermissionsApi::class)
@Composable
override fun Body() {
- val alarms = viewModel.selectedAlarms.collectAsStateLifecycleAware()
+ val viewState = vm.viewState.collectAsStateLifecycleAware()
+ val current: ViewState = viewState.value
+ val notificationPermissions = if (atLeastTiramisu()) {
+ rememberPermissionState(
+ android.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)) {
@@ -181,26 +236,6 @@ class ReminderControlSet : TaskEditControlComposeFragment() {
}
Spacer(modifier = Modifier.height(8.dp))
}
-
- val openCustomDialog = remember { showCustomDialog }
- AddReminderDialog.AddCustomReminderDialog(
- openCustomDialog,
- addAlarm = this::addAlarmRow,
- closeDialog = {
- openCustomDialog.value = false
- AndroidUtilities.hideKeyboard(activity)
- }
- )
-
- val openRandomDialog = remember { showRandomDialog }
- AddReminderDialog.AddRandomReminderDialog(
- openRandomDialog,
- addAlarm = this::addAlarmRow,
- closeDialog = {
- openRandomDialog.value = false
- AndroidUtilities.hideKeyboard(activity)
- }
- )
}
override val icon = R.drawable.ic_outline_notifications_24px
@@ -235,14 +270,6 @@ class ReminderControlSet : TaskEditControlComposeFragment() {
startActivityForResult(intent, REQUEST_NEW_ALARM)
}
- private fun addCustomAlarm() {
- showCustomDialog.value = true
- }
-
- private fun addRandomAlarm() {
- showRandomDialog.value = true
- }
-
private val options: List
get() {
val options: MutableList = ArrayList()
@@ -264,8 +291,6 @@ class ReminderControlSet : TaskEditControlComposeFragment() {
companion object {
const val TAG = R.string.TEA_ctrl_reminders_pref
private const val REQUEST_NEW_ALARM = 12152
- private const val CUSTOM_DIALOG_VISIBLE = "custom_dialog_visible"
- private const val RANDOM_DIALOG_VISIBLE = "random_dialog_visible"
}
}
diff --git a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSetViewModel.kt b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSetViewModel.kt
new file mode 100644
index 000000000..c37760c04
--- /dev/null
+++ b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSetViewModel.kt
@@ -0,0 +1,25 @@
+package com.todoroo.astrid.ui
+
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class ReminderControlSetViewModel : ViewModel() {
+ private val _viewState = MutableStateFlow(ReminderControlSet.ViewState())
+
+ val viewState: StateFlow
+ get() = _viewState.asStateFlow()
+
+ fun showCustomDialog(visible: Boolean) {
+ _viewState.value = _viewState.value.copy(
+ showCustomDialog = visible
+ )
+ }
+
+ fun showRandomDialog(visible: Boolean) {
+ _viewState.value = _viewState.value.copy(
+ showRandomDialog = visible
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/compose/AddReminderDialog.kt b/app/src/main/java/org/tasks/compose/AddReminderDialog.kt
index be2c473ce..8e929cd9f 100644
--- a/app/src/main/java/org/tasks/compose/AddReminderDialog.kt
+++ b/app/src/main/java/org/tasks/compose/AddReminderDialog.kt
@@ -3,31 +3,14 @@ package org.tasks.compose
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.AlertDialog
-import androidx.compose.material.Divider
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.OutlinedTextField
-import androidx.compose.material.RadioButton
-import androidx.compose.material.Text
-import androidx.compose.material.TextFieldDefaults
+import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Autorenew
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.ExperimentalComposeUiApi
@@ -56,13 +39,13 @@ import java.util.concurrent.TimeUnit
object AddReminderDialog {
@Composable
fun AddRandomReminderDialog(
- openDialog: MutableState,
+ openDialog: Boolean,
addAlarm: (Alarm) -> Unit,
closeDialog: () -> Unit,
) {
val time = rememberSaveable { mutableStateOf(15) }
val units = rememberSaveable { mutableStateOf(0) }
- if (openDialog.value) {
+ if (openDialog) {
AlertDialog(
onDismissRequest = closeDialog,
text = { AddRandomReminder(openDialog, time, units) },
@@ -89,7 +72,7 @@ object AddReminderDialog {
@Composable
fun AddCustomReminderDialog(
- openDialog: MutableState,
+ openDialog: Boolean,
addAlarm: (Alarm) -> Unit,
closeDialog: () -> Unit,
) {
@@ -99,7 +82,7 @@ object AddReminderDialog {
val interval = rememberSaveable { mutableStateOf(0) }
val recurringUnits = rememberSaveable { mutableStateOf(0) }
val repeat = rememberSaveable { mutableStateOf(0) }
- if (openDialog.value) {
+ if (openDialog) {
if (!openRecurringDialog.value) {
AlertDialog(
onDismissRequest = closeDialog,
@@ -179,7 +162,7 @@ object AddReminderDialog {
onDismissRequest = closeDialog,
text = {
AddRecurringReminder(
- openDialog,
+ openDialog.value,
interval,
units,
repeat,
@@ -209,7 +192,7 @@ object AddReminderDialog {
@Composable
fun AddRandomReminder(
- visible: MutableState,
+ visible: Boolean,
time: MutableState,
units: MutableState,
) {
@@ -237,7 +220,7 @@ object AddReminderDialog {
@Composable
fun AddCustomReminder(
- visible: MutableState,
+ visible: Boolean,
time: MutableState,
units: MutableState,
interval: MutableState,
@@ -313,7 +296,7 @@ object AddReminderDialog {
@Composable
fun AddRecurringReminder(
- openDialog: MutableState,
+ openDialog: Boolean,
interval: MutableState,
units: MutableState,
repeat: MutableState
@@ -374,7 +357,7 @@ object AddReminderDialog {
@ExperimentalComposeUiApi
@Composable
-fun ShowKeyboard(visible: MutableState, focusRequester: FocusRequester) {
+fun ShowKeyboard(visible: Boolean, focusRequester: FocusRequester) {
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(visible) {
focusRequester.freeFocus()
@@ -484,7 +467,7 @@ fun BodyText(modifier: Modifier = Modifier, text: String) {
fun AddCustomReminderOne() =
MdcTheme {
AddReminderDialog.AddCustomReminder(
- visible = remember { mutableStateOf(true) },
+ visible = true,
time = remember { mutableStateOf(1) },
units = remember { mutableStateOf(0) },
interval = remember { mutableStateOf(0) },
@@ -501,7 +484,7 @@ fun AddCustomReminderOne() =
fun AddCustomReminder() =
MdcTheme {
AddReminderDialog.AddCustomReminder(
- visible = remember { mutableStateOf(true) },
+ visible = true,
time = remember { mutableStateOf(15) },
units = remember { mutableStateOf(1) },
interval = remember { mutableStateOf(0) },
@@ -518,7 +501,7 @@ fun AddCustomReminder() =
fun AddRepeatingReminderOne() =
MdcTheme {
AddReminderDialog.AddRecurringReminder(
- openDialog = remember { mutableStateOf(true) },
+ openDialog = true,
interval = remember { mutableStateOf(1) },
units = remember { mutableStateOf(0) },
repeat = remember { mutableStateOf(1) },
@@ -532,7 +515,7 @@ fun AddRepeatingReminderOne() =
fun AddRepeatingReminder() =
MdcTheme {
AddReminderDialog.AddRecurringReminder(
- openDialog = remember { mutableStateOf(true) },
+ openDialog = true,
interval = remember { mutableStateOf(15) },
units = remember { mutableStateOf(1) },
repeat = remember { mutableStateOf(4) },
@@ -546,7 +529,7 @@ fun AddRepeatingReminder() =
fun AddRandomReminderOne() =
MdcTheme {
AddReminderDialog.AddRandomReminder(
- visible = remember { mutableStateOf(true) },
+ visible = true,
time = remember { mutableStateOf(1) },
units = remember { mutableStateOf(0) }
)
@@ -559,7 +542,7 @@ fun AddRandomReminderOne() =
fun AddRandomReminder() =
MdcTheme {
AddReminderDialog.AddRandomReminder(
- visible = remember { mutableStateOf(true) },
+ visible = true,
time = remember { mutableStateOf(15) },
units = remember { mutableStateOf(1) }
)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e61904608..cd3ef4c7b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -734,4 +734,6 @@ File %1$s contained %2$s.\n\n
Dismiss
Too much information?
You can customize this screen by rearranging or removing fields
+ Enable reminders
+ Reminders are disabled in Android Settings
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
index 6b9c4b263..8ca7037ce 100644
--- a/buildSrc/src/main/kotlin/Versions.kt
+++ b/buildSrc/src/main/kotlin/Versions.kt
@@ -20,4 +20,5 @@ object Versions {
const val markwon = "4.6.2"
const val compose = "1.2.0-rc02"
const val compose_theme_adapter = "1.1.11"
+ const val accompanist = "0.24.12-rc"
}
diff --git a/deps_fdroid.txt b/deps_fdroid.txt
index 3c10ff104..81febf895 100644
--- a/deps_fdroid.txt
+++ b/deps_fdroid.txt
@@ -556,16 +556,25 @@
+| +--- androidx.compose.material:material:1.2.0-beta03 -> 1.2.0-rc02 (*)
+| +--- com.google.android.material:material:1.7.0-alpha02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 (*)
-++--- androidx.activity:activity-compose:1.4.0
+++--- androidx.activity:activity-compose:1.4.0 -> 1.5.0-rc01
++| +--- androidx.activity:activity-ktx:1.5.0-rc01 -> 1.6.0-alpha05 (*)
+| +--- androidx.compose.runtime:runtime:1.0.1 -> 1.2.0-rc02 (*)
+| +--- androidx.compose.runtime:runtime-saveable:1.0.1 -> 1.2.0-rc02 (*)
-+| +--- androidx.activity:activity-ktx:1.4.0 -> 1.6.0-alpha05 (*)
+| +--- androidx.compose.ui:ui:1.0.1 -> 1.2.0-rc02 (*)
-+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.31 -> 1.6.21 (*)
++| +--- androidx.lifecycle:lifecycle-common-java8:2.5.0-rc01 (*)
++| \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 (*)
++--- androidx.compose.material:material-icons-extended:1.2.0-rc02
+| +--- androidx.compose.material:material-icons-core:1.2.0-rc02 (*)
+| +--- androidx.compose.runtime:runtime:1.2.0-rc02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21
-+\--- com.google.accompanist:accompanist-flowlayout:0.24.11-rc
-+ +--- androidx.compose.foundation:foundation:1.2.0-rc01 -> 1.2.0-rc02 (*)
-+ \--- androidx.compose.ui:ui-util:1.2.0-rc01 -> 1.2.0-rc02 (*)
+++--- com.google.accompanist:accompanist-flowlayout:0.24.12-rc
++| +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*)
++| \--- androidx.compose.ui:ui-util:1.2.0-rc02 (*)
++\--- com.google.accompanist:accompanist-permissions:0.24.12-rc
++ +--- androidx.activity:activity-compose:1.5.0-rc01 (*)
++ +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*)
++ +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0 -> 1.6.1 (*)
++ \--- io.github.aakira:napier:1.4.1
++ \--- io.github.aakira:napier-android:1.4.1
++ +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.32 -> 1.6.21 (*)
++ \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32 -> 1.6.21
diff --git a/deps_googleplay.txt b/deps_googleplay.txt
index dafe3040c..315d84444 100644
--- a/deps_googleplay.txt
+++ b/deps_googleplay.txt
@@ -692,16 +692,25 @@
+| +--- androidx.compose.material:material:1.2.0-beta03 -> 1.2.0-rc02 (*)
+| +--- com.google.android.material:material:1.7.0-alpha02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 (*)
-++--- androidx.activity:activity-compose:1.4.0
+++--- androidx.activity:activity-compose:1.4.0 -> 1.5.0-rc01
++| +--- androidx.activity:activity-ktx:1.5.0-rc01 -> 1.6.0-alpha05 (*)
+| +--- androidx.compose.runtime:runtime:1.0.1 -> 1.2.0-rc02 (*)
+| +--- androidx.compose.runtime:runtime-saveable:1.0.1 -> 1.2.0-rc02 (*)
-+| +--- androidx.activity:activity-ktx:1.4.0 -> 1.6.0-alpha05 (*)
+| +--- androidx.compose.ui:ui:1.0.1 -> 1.2.0-rc02 (*)
-+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.31 -> 1.6.21 (*)
++| +--- androidx.lifecycle:lifecycle-common-java8:2.5.0-rc01 (*)
++| \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 (*)
++--- androidx.compose.material:material-icons-extended:1.2.0-rc02
+| +--- androidx.compose.material:material-icons-core:1.2.0-rc02 (*)
+| +--- androidx.compose.runtime:runtime:1.2.0-rc02 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21
-+\--- com.google.accompanist:accompanist-flowlayout:0.24.11-rc
-+ +--- androidx.compose.foundation:foundation:1.2.0-rc01 -> 1.2.0-rc02 (*)
-+ \--- androidx.compose.ui:ui-util:1.2.0-rc01 -> 1.2.0-rc02 (*)
+++--- com.google.accompanist:accompanist-flowlayout:0.24.12-rc
++| +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*)
++| \--- androidx.compose.ui:ui-util:1.2.0-rc02 (*)
++\--- com.google.accompanist:accompanist-permissions:0.24.12-rc
++ +--- androidx.activity:activity-compose:1.5.0-rc01 (*)
++ +--- androidx.compose.foundation:foundation:1.2.0-rc02 (*)
++ +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0 -> 1.6.1 (*)
++ \--- io.github.aakira:napier:1.4.1
++ \--- io.github.aakira:napier-android:1.4.1
++ +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.32 -> 1.6.21 (*)
++ \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32 -> 1.6.21