Adds an explainer to install UnifiedPush apps if they are not installed.

This commit is contained in:
Vitor Pamplona
2023-10-27 13:05:47 -04:00
parent 56af44b00b
commit 6a8c756f7a
7 changed files with 153 additions and 50 deletions
@@ -1,56 +1,133 @@
package com.vitorpamplona.amethyst.ui.components package com.vitorpamplona.amethyst.ui.components
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material3.Material3RichText
import com.halilibo.richtext.ui.resolveDefaults
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckifItNeedsToRequestNotificationPermission
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsRow
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesViewModel) { fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesViewModel) {
if (!sharedPreferencesViewModel.sharedPrefs.dontShowPushNotificationSelector) { val notificationPermissionState = CheckifItNeedsToRequestNotificationPermission(sharedPreferencesViewModel)
val context = LocalContext.current
var distributorPresent by remember { if (notificationPermissionState.status.isGranted) {
mutableStateOf(PushDistributorHandler.savedDistributorExists()) if (!sharedPreferencesViewModel.sharedPrefs.dontShowPushNotificationSelector) {
} val context = LocalContext.current
if (!distributorPresent) { var distributorPresent by remember {
LoadDistributors() { currentDistributor, list, readableListWithExplainer -> mutableStateOf(PushDistributorHandler.savedDistributorExists())
if (!readableListWithExplainer.isEmpty()) { }
SpinnerSelectionDialog( if (!distributorPresent) {
title = stringResource(id = R.string.select_push_server), LoadDistributors() { currentDistributor, list, readableListWithExplainer ->
options = readableListWithExplainer, if (readableListWithExplainer.size > 1) {
onSelect = { index -> SpinnerSelectionDialog(
if (list[index] == "None") { title = stringResource(id = R.string.select_push_server),
PushDistributorHandler.forceRemoveDistributor(context) options = readableListWithExplainer,
sharedPreferencesViewModel.dontShowPushNotificationSelector() onSelect = { index ->
} else { if (list[index] == "None") {
val fullDistributorName = list[index] PushDistributorHandler.forceRemoveDistributor(context)
PushDistributorHandler.saveDistributor(fullDistributorName) sharedPreferencesViewModel.dontAskForNotificationPermissions()
} sharedPreferencesViewModel.dontShowPushNotificationSelector()
distributorPresent = true } else {
Log.d("Amethyst", "NotificationScreen: Distributor registered.") val fullDistributorName = list[index]
}, PushDistributorHandler.saveDistributor(fullDistributorName)
onDismiss = { }
distributorPresent = true distributorPresent = true
Log.d("Amethyst", "NotificationScreen: Distributor dialog dismissed.") Log.d("Amethyst", "NotificationScreen: Distributor registered.")
} },
) onDismiss = {
} distributorPresent = true
Log.d("Amethyst", "NotificationScreen: Distributor dialog dismissed.")
}
)
} else {
AlertDialog(
onDismissRequest = {
distributorPresent = true
},
title = {
Text(stringResource(R.string.push_server_insteall_app))
},
text = {
Material3RichText(
style = RichTextStyle().resolveDefaults()
) {
Markdown(
content = stringResource(R.string.push_server_insteall_app_description)
)
}
},
confirmButton = {
Row(
modifier = Modifier
.padding(all = 8.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextButton(onClick = {
distributorPresent = true
sharedPreferencesViewModel.dontShowPushNotificationSelector()
}) {
Text(stringResource(R.string.quick_action_dont_show_again_button))
}
Button(onClick = {
distributorPresent = true
}, contentPadding = PaddingValues(horizontal = 16.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
Spacer(Modifier.width(8.dp))
Text(stringResource(R.string.error_dialog_button_ok))
}
}
}
}
)
}
}
} else {
val currentDistributor = PushDistributorHandler.getSavedDistributor()
PushDistributorHandler.saveDistributor(currentDistributor)
} }
} else {
val currentDistributor = PushDistributorHandler.getSavedDistributor()
PushDistributorHandler.saveDistributor(currentDistributor)
} }
} }
} }
@@ -100,6 +177,7 @@ fun PushNotificationSettingsRow(sharedPreferencesViewModel: SharedPreferencesVie
selectedIndex = list.indexOf(currentDistributor) selectedIndex = list.indexOf(currentDistributor)
) { index -> ) { index ->
if (list[index] == "None") { if (list[index] == "None") {
sharedPreferencesViewModel.dontAskForNotificationPermissions()
sharedPreferencesViewModel.dontShowPushNotificationSelector() sharedPreferencesViewModel.dontShowPushNotificationSelector()
PushDistributorHandler.forceRemoveDistributor(context) PushDistributorHandler.forceRemoveDistributor(context)
} else { } else {
@@ -371,6 +371,7 @@ object LocalPreferences {
automaticallyShowUrlPreview, automaticallyShowUrlPreview,
automaticallyHideNavigationBars, automaticallyHideNavigationBars,
automaticallyShowProfilePictures, automaticallyShowProfilePictures,
false,
false false
) )
} }
@@ -12,7 +12,8 @@ data class Settings(
val automaticallyShowUrlPreview: ConnectivityType = ConnectivityType.ALWAYS, val automaticallyShowUrlPreview: ConnectivityType = ConnectivityType.ALWAYS,
val automaticallyHideNavigationBars: BooleanType = BooleanType.ALWAYS, val automaticallyHideNavigationBars: BooleanType = BooleanType.ALWAYS,
val automaticallyShowProfilePictures: ConnectivityType = ConnectivityType.ALWAYS, val automaticallyShowProfilePictures: ConnectivityType = ConnectivityType.ALWAYS,
val dontShowPushNotificationSelector: Boolean = false val dontShowPushNotificationSelector: Boolean = false,
val dontAskForNotificationPermissions: Boolean = false
) )
enum class ThemeType(val screenCode: Int, val resourceId: Int) { enum class ThemeType(val screenCode: Int, val resourceId: Int) {
@@ -31,6 +31,7 @@ class SettingsState() {
var automaticallyHideNavigationBars by mutableStateOf(BooleanType.ALWAYS) var automaticallyHideNavigationBars by mutableStateOf(BooleanType.ALWAYS)
var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS) var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS)
var dontShowPushNotificationSelector by mutableStateOf<Boolean>(false) var dontShowPushNotificationSelector by mutableStateOf<Boolean>(false)
var dontAskForNotificationPermissions by mutableStateOf<Boolean>(false)
var isOnMobileData: State<Boolean> = mutableStateOf(false) var isOnMobileData: State<Boolean> = mutableStateOf(false)
@@ -87,6 +88,8 @@ class SharedPreferencesViewModel : ViewModel() {
sharedPrefs.automaticallyShowUrlPreview = savedSettings.automaticallyShowUrlPreview sharedPrefs.automaticallyShowUrlPreview = savedSettings.automaticallyShowUrlPreview
sharedPrefs.automaticallyHideNavigationBars = savedSettings.automaticallyHideNavigationBars sharedPrefs.automaticallyHideNavigationBars = savedSettings.automaticallyHideNavigationBars
sharedPrefs.automaticallyShowProfilePictures = savedSettings.automaticallyShowProfilePictures sharedPrefs.automaticallyShowProfilePictures = savedSettings.automaticallyShowProfilePictures
sharedPrefs.dontShowPushNotificationSelector = savedSettings.dontShowPushNotificationSelector
sharedPrefs.dontAskForNotificationPermissions = savedSettings.dontAskForNotificationPermissions
updateLanguageInTheUI() updateLanguageInTheUI()
} }
@@ -154,12 +157,19 @@ class SharedPreferencesViewModel : ViewModel() {
} }
fun dontShowPushNotificationSelector() { fun dontShowPushNotificationSelector() {
if (!sharedPrefs.dontShowPushNotificationSelector) { if (sharedPrefs.dontShowPushNotificationSelector == false) {
sharedPrefs.dontShowPushNotificationSelector = true sharedPrefs.dontShowPushNotificationSelector = true
saveSharedSettings() saveSharedSettings()
} }
} }
fun dontAskForNotificationPermissions() {
if (sharedPrefs.dontAskForNotificationPermissions == false) {
sharedPrefs.dontAskForNotificationPermissions = true
saveSharedSettings()
}
}
fun updateConnectivityStatusState(isOnMobileDataState: State<Boolean>) { fun updateConnectivityStatusState(isOnMobileDataState: State<Boolean>) {
if (sharedPrefs.isOnMobileData != isOnMobileDataState) { if (sharedPrefs.isOnMobileData != isOnMobileDataState) {
sharedPrefs.isOnMobileData = isOnMobileDataState sharedPrefs.isOnMobileData = isOnMobileDataState
@@ -186,7 +196,8 @@ class SharedPreferencesViewModel : ViewModel() {
sharedPrefs.automaticallyShowUrlPreview, sharedPrefs.automaticallyShowUrlPreview,
sharedPrefs.automaticallyHideNavigationBars, sharedPrefs.automaticallyHideNavigationBars,
sharedPrefs.automaticallyShowProfilePictures, sharedPrefs.automaticallyShowProfilePictures,
sharedPrefs.dontShowPushNotificationSelector sharedPrefs.dontShowPushNotificationSelector,
sharedPrefs.dontAskForNotificationPermissions
) )
) )
} }
@@ -29,6 +29,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.patrykandpatrick.vico.compose.axis.axisLabelComponent import com.patrykandpatrick.vico.compose.axis.axisLabelComponent
@@ -79,8 +80,6 @@ fun NotificationScreen(
WatchAccountForNotifications(notifFeedViewModel, accountViewModel) WatchAccountForNotifications(notifFeedViewModel, accountViewModel)
CheckifItNeedsToRequestNotificationPermission()
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifeCycleOwner) { DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
@@ -114,26 +113,29 @@ fun NotificationScreen(
} }
} }
// TODO: Turn this into an Account flag
var hasAlreadyAskedNotificationPermissions = false
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
fun CheckifItNeedsToRequestNotificationPermission() { fun CheckifItNeedsToRequestNotificationPermission(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasAlreadyAskedNotificationPermissions) { sharedPreferencesViewModel: SharedPreferencesViewModel
val notificationPermissionState = rememberPermissionState( ): PermissionState {
Manifest.permission.POST_NOTIFICATIONS val notificationPermissionState = rememberPermissionState(
) Manifest.permission.POST_NOTIFICATIONS
)
if (!notificationPermissionState.status.isGranted) { if (!sharedPreferencesViewModel.sharedPrefs.dontAskForNotificationPermissions) {
hasAlreadyAskedNotificationPermissions = true if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (!notificationPermissionState.status.isGranted) {
sharedPreferencesViewModel.dontAskForNotificationPermissions()
// This will pause the APP, including the connection with relays. // This will pause the APP, including the connection with relays.
LaunchedEffect(notificationPermissionState) { LaunchedEffect(notificationPermissionState) {
notificationPermissionState.launchPermissionRequest() notificationPermissionState.launchPermissionRequest()
}
} }
} }
} }
return notificationPermissionState
} }
@Composable @Composable
+7 -1
View File
@@ -646,10 +646,16 @@
<string name="read_only_user">Read-only user</string> <string name="read_only_user">Read-only user</string>
<string name="no_reactions_setup">No reactions setup</string> <string name="no_reactions_setup">No reactions setup</string>
<string name="select_push_server">Select a Push Notification distributor</string> <string name="select_push_server">Select a UnifiedPush App</string>
<string name="push_server_title">Push Notification</string> <string name="push_server_title">Push Notification</string>
<string name="push_server_explainer">From installed UnifiedPush apps</string> <string name="push_server_explainer">From installed UnifiedPush apps</string>
<string name="push_server_none">None</string> <string name="push_server_none">None</string>
<string name="push_server_none_explainer">Disables Push Notifications</string> <string name="push_server_none_explainer">Disables Push Notifications</string>
<string name="push_server_uses_app_explainer">Uses app %1$s</string> <string name="push_server_uses_app_explainer">Uses app %1$s</string>
<string name="push_server_insteall_app">Push Notification Setup</string>
<string name="push_server_insteall_app_description">
To receive push notifications, install any app that supports [Unified Push](https://unifiedpush.org/), such as [Nfty](https://ntfy.sh/).
After installing, select the app you want to use in the Settings.
</string>
</resources> </resources>
@@ -1,10 +1,14 @@
package com.vitorpamplona.amethyst.ui.components package com.vitorpamplona.amethyst.ui.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckifItNeedsToRequestNotificationPermission
@OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesViewModel) { fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesViewModel) {
CheckifItNeedsToRequestNotificationPermission(sharedPreferencesViewModel)
} }
@Composable @Composable