Android 13 runtime notification permissions

pull/1917/head
Alex Baker 3 years ago
parent 99b1c2e38d
commit 1fbc2b1661

@ -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}") {

@ -6,6 +6,8 @@
<!-- ================================================== Used Permissions = -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- ********************* -->
<!-- maps and place search -->
<!-- ********************* -->

@ -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");

@ -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<String>
get() {
val options: MutableList<String> = 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"
}
}

@ -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<ReminderControlSet.ViewState>
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
)
}
}

@ -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<Boolean>,
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<Boolean>,
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<Boolean>,
visible: Boolean,
time: MutableState<Int>,
units: MutableState<Int>,
) {
@ -237,7 +220,7 @@ object AddReminderDialog {
@Composable
fun AddCustomReminder(
visible: MutableState<Boolean>,
visible: Boolean,
time: MutableState<Int>,
units: MutableState<Int>,
interval: MutableState<Int>,
@ -313,7 +296,7 @@ object AddReminderDialog {
@Composable
fun AddRecurringReminder(
openDialog: MutableState<Boolean>,
openDialog: Boolean,
interval: MutableState<Int>,
units: MutableState<Int>,
repeat: MutableState<Int>
@ -374,7 +357,7 @@ object AddReminderDialog {
@ExperimentalComposeUiApi
@Composable
fun ShowKeyboard(visible: MutableState<Boolean>, 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) }
)

@ -734,4 +734,6 @@ File %1$s contained %2$s.\n\n
<string name="dismiss">Dismiss</string>
<string name="hint_customize_edit_title">Too much information?</string>
<string name="hint_customize_edit_body">You can customize this screen by rearranging or removing fields</string>
<string name="enable_reminders">Enable reminders</string>
<string name="enable_reminders_description">Reminders are disabled in Android Settings</string>
</resources>

@ -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"
}

@ -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

@ -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

Loading…
Cancel
Save