diff --git a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index caa5489453..810bb9aa71 100644 --- a/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/app/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -37,6 +37,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.DEBUG_SH import org.session.libsession.utilities.TextSecurePreferences.Companion.ENVIRONMENT import org.session.libsession.utilities.TextSecurePreferences.Companion.FOLLOW_SYSTEM_SETTINGS import org.session.libsession.utilities.TextSecurePreferences.Companion.FORCED_SHORT_TTL +import org.session.libsession.utilities.TextSecurePreferences.Companion.HAS_CHECKED_DOZE_WHITELIST import org.session.libsession.utilities.TextSecurePreferences.Companion.HAS_COPIED_DONATION_URL import org.session.libsession.utilities.TextSecurePreferences.Companion.HAS_DONATED import org.session.libsession.utilities.TextSecurePreferences.Companion.HAS_HIDDEN_MESSAGE_REQUESTS @@ -114,7 +115,6 @@ interface TextSecurePreferences { fun setHasSeenGIFMetaDataWarning() fun isGifSearchInGridLayout(): Boolean fun setIsGifSearchInGridLayout(isGrid: Boolean) - fun getNotificationPriority(): Int fun getMessageBodyTextSize(): Int fun setPreferredCameraDirection(value: CameraSelector) fun getPreferredCameraDirection(): CameraSelector @@ -234,6 +234,8 @@ interface TextSecurePreferences { fun setSubscriptionProvider(provider: String) fun getSubscriptionProvider(): String? + fun hasCheckedDozeWhitelist(): Boolean + fun setHasCheckedDozeWhitelist(hasChecked: Boolean) fun hasDonated(): Boolean fun setHasDonated(hasDonated: Boolean) fun hasCopiedDonationURL(): Boolean @@ -311,7 +313,6 @@ interface TextSecurePreferences { const val LOCAL_REGISTRATION_ID_PREF = "pref_local_registration_id" const val REPEAT_ALERTS_PREF = "pref_repeat_alerts" const val NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy" - const val NOTIFICATION_PRIORITY_PREF = "pref_notification_priority" const val MEDIA_DOWNLOAD_MOBILE_PREF = "pref_media_download_mobile" const val MEDIA_DOWNLOAD_WIFI_PREF = "pref_media_download_wifi" const val MEDIA_DOWNLOAD_ROAMING_PREF = "pref_media_download_roaming" @@ -422,6 +423,8 @@ interface TextSecurePreferences { const val SUBSCRIPTION_PROVIDER = "session_subscription_provider" const val DEBUG_AVATAR_REUPLOAD = "debug_avatar_reupload" + const val HAS_CHECKED_DOZE_WHITELIST = "has_checked_doze_whitelist" + // Donation const val HAS_DONATED = "has_donated" const val HAS_COPIED_DONATION_URL = "has_copied_donation_url" @@ -1198,11 +1201,6 @@ class AppTextSecurePreferences @Inject constructor( setBooleanPreference(TextSecurePreferences.GIF_GRID_LAYOUT, isGrid) } - override fun getNotificationPriority(): Int { - return getStringPreference( - TextSecurePreferences.NOTIFICATION_PRIORITY_PREF, NotificationCompat.PRIORITY_HIGH.toString())!!.toInt() - } - override fun getMessageBodyTextSize(): Int { return getStringPreference(TextSecurePreferences.MESSAGE_BODY_TEXT_SIZE_PREF, "16")!!.toInt() } @@ -1839,6 +1837,14 @@ class AppTextSecurePreferences @Inject constructor( }) } + override fun hasCheckedDozeWhitelist(): Boolean { + return getBooleanPreference(HAS_CHECKED_DOZE_WHITELIST, false) + } + + override fun setHasCheckedDozeWhitelist(hasChecked: Boolean) { + setBooleanPreference(HAS_CHECKED_DOZE_WHITELIST, hasChecked) + } + override fun hasDonated(): Boolean { return getBooleanPreference(HAS_DONATED, false) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 4ebfc1c1e1..ebbf1f95ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -85,6 +85,9 @@ import org.thoughtcrime.securesms.reviews.ui.InAppReviewViewModel import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.tokenpage.TokenPageNotificationManager import org.thoughtcrime.securesms.ui.components.Avatar +import org.thoughtcrime.securesms.ui.findActivity +import org.thoughtcrime.securesms.ui.isWhitelistedFromDoze +import org.thoughtcrime.securesms.ui.requestDozeWhitelist import org.thoughtcrime.securesms.ui.setThemedContent import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.util.AvatarUtils @@ -250,6 +253,10 @@ class HomeActivity : ScreenLockActionBarActivity(), ) ) } + + is HomeViewModel.UiEvent.ShowWhiteListSystemDialog -> { + requestDozeWhitelist() + } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt index 186f9efc6e..e6c3c06383 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt @@ -19,12 +19,17 @@ import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.thoughtcrime.securesms.home.HomeViewModel.Commands.* import org.thoughtcrime.securesms.home.startconversation.StartConversationSheet import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsDestination +import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.AnimatedSessionProCTA import org.thoughtcrime.securesms.ui.CTAFeature +import org.thoughtcrime.securesms.ui.DialogButtonData +import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.OpenURLAlertDialog import org.thoughtcrime.securesms.ui.PinProCTA import org.thoughtcrime.securesms.ui.SimpleSessionProCTA import org.thoughtcrime.securesms.ui.UserProfileModal +import org.thoughtcrime.securesms.ui.components.annotatedStringResource +import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme @Composable @@ -33,6 +38,42 @@ fun HomeDialogs( sendCommand: (HomeViewModel.Commands) -> Unit ) { SessionMaterialTheme { + // Simple dialogs + if (dialogsState.showSimpleDialog != null) { + val buttons = mutableListOf() + if(dialogsState.showSimpleDialog.positiveText != null) { + buttons.add( + DialogButtonData( + text = GetString(dialogsState.showSimpleDialog.positiveText), + color = if (dialogsState.showSimpleDialog.positiveStyleDanger) LocalColors.current.danger + else LocalColors.current.text, + qaTag = dialogsState.showSimpleDialog.positiveQaTag, + onClick = dialogsState.showSimpleDialog.onPositive + ) + ) + } + if(dialogsState.showSimpleDialog.negativeText != null){ + buttons.add( + DialogButtonData( + text = GetString(dialogsState.showSimpleDialog.negativeText), + qaTag = dialogsState.showSimpleDialog.negativeQaTag, + onClick = dialogsState.showSimpleDialog.onNegative + ) + ) + } + + AlertDialog( + onDismissRequest = { + // hide dialog + sendCommand(HideSimpleDialog) + }, + title = annotatedStringResource(dialogsState.showSimpleDialog.title), + text = annotatedStringResource(dialogsState.showSimpleDialog.message), + showCloseButton = dialogsState.showSimpleDialog.showXIcon, + buttons = buttons + ) + } + // pin CTA if(dialogsState.pinCTA != null){ PinProCTA( diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 3333e6ad61..3eafbf1190 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -4,11 +4,13 @@ import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope +import com.squareup.phrase.Phrase import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -30,6 +32,7 @@ import network.loki.messenger.libsession_util.PRIORITY_HIDDEN import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.Address +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.displayName import org.session.libsignal.utilities.AccountId @@ -40,10 +43,15 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.debugmenu.DebugLogGroup import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsDestination +import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository +import org.thoughtcrime.securesms.ui.SimpleDialogData +import org.thoughtcrime.securesms.ui.findActivity +import org.thoughtcrime.securesms.ui.isWhitelistedFromDoze +import org.thoughtcrime.securesms.ui.requestDozeWhitelist import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.DonationManager import org.thoughtcrime.securesms.util.DonationManager.Companion.URL_DONATE @@ -161,6 +169,41 @@ class HomeViewModel @Inject constructor( private var userProfileModalUtils: UserProfileUtils? = null init { + // check for white list status in case of slow mode + if(!prefs.hasCheckedDozeWhitelist() // the user has not yet seen the dialog + && !prefs.pushEnabled.value // the user is in slow mode + && !context.isWhitelistedFromDoze() // the user isn't yet whitelisted + ){ + prefs.setHasCheckedDozeWhitelist(true) + viewModelScope.launch { + delay(1500) + _dialogsState.update { + it.copy( + showSimpleDialog = SimpleDialogData( + title = Phrase.from(context, R.string.runSessionBackground) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .format().toString(), + message = Phrase.from(context, R.string.runSessionBackgroundDescription) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .format().toString(), + positiveText = context.getString(R.string.allow), + negativeText = context.getString(R.string.cancel), + positiveQaTag = context.getString(R.string.qa_conversation_settings_dialog_whitelist_confirm), + negativeQaTag = context.getString(R.string.qa_conversation_settings_dialog_whitelist_cancel), + positiveStyleDanger = false, + onPositive = { + // show system whitelist dialog + viewModelScope.launch { + _uiEvents.emit(UiEvent.ShowWhiteListSystemDialog) + } + }, + onNegative = {} + ) + ) + } + } + } + // observe subscription status viewModelScope.launch { proStatusManager.proDataState.collect { subscription -> @@ -331,6 +374,10 @@ class HomeViewModel @Inject constructor( } } + is Commands.HideSimpleDialog -> { + _dialogsState.update { it.copy(showSimpleDialog = null) } + } + is Commands.HideDonationCTADialog -> { _dialogsState.update { it.copy(donationCTA = false) } } @@ -392,6 +439,7 @@ class HomeViewModel @Inject constructor( val showStartConversationSheet: StartConversationSheetData? = null, val proExpiringCTA: ProExpiringCTA? = null, val proExpiredCTA: Boolean = false, + val showSimpleDialog: SimpleDialogData? = null, val donationCTA: Boolean = false, val showUrlDialog: String? = null, ) @@ -411,6 +459,7 @@ class HomeViewModel @Inject constructor( sealed interface UiEvent { data class OpenProSettings(val start: ProSettingsDestination) : UiEvent + data object ShowWhiteListSystemDialog: UiEvent // once confirmed, this is for the system whitelist dialog } sealed interface Commands { @@ -430,6 +479,8 @@ class HomeViewModel @Inject constructor( data object ShowStartConversationSheet : Commands data object HideStartConversationSheet : Commands + data object HideSimpleDialog: Commands + data class GotoProSettings( val destination: ProSettingsDestination ): Commands diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt index 61f3af1fd1..10d75fa643 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.preferences import android.annotation.SuppressLint import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Intent import android.media.RingtoneManager import android.net.Uri @@ -9,16 +10,40 @@ import android.os.AsyncTask import android.os.Bundle import android.provider.Settings import android.text.TextUtils -import androidx.preference.ListPreference +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.preference.Preference +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import network.loki.messenger.BuildConfig import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.components.SwitchPreferenceCompat import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.preferences.widgets.DropDownPreference +import org.thoughtcrime.securesms.ui.AlertDialog +import org.thoughtcrime.securesms.ui.DialogButtonData +import org.thoughtcrime.securesms.ui.GetString +import org.thoughtcrime.securesms.ui.isWhitelistedFromDoze +import org.thoughtcrime.securesms.ui.requestDozeWhitelist +import org.thoughtcrime.securesms.ui.setThemedContent +import org.thoughtcrime.securesms.ui.theme.LocalColors import java.util.Arrays import javax.inject.Inject @@ -27,8 +52,139 @@ class NotificationsPreferenceFragment : CorrectedPreferenceFragment() { @Inject lateinit var prefs: TextSecurePreferences + private var showWhitelistEnableDialog by mutableStateOf(false) + private var showWhitelistDisableDialog by mutableStateOf(false) + + private var whiteListControl: SwitchPreferenceCompat? = null + + private var composeView: ComposeView? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + // We will wrap the existing screen in a framelayout in order to add custom compose content + val preferenceView = super.onCreateView(inflater, container, savedInstanceState) + + val wrapper = FrameLayout(requireContext()).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + + wrapper.addView( + preferenceView, + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + ) + + composeView = ComposeView(requireContext()) + wrapper.addView( + composeView, + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.TOP + ) + ) + + return wrapper + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + //set up compose content + composeView?.apply { + setViewCompositionStrategy( + ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed + ) + setThemedContent { + if(showWhitelistEnableDialog) { + AlertDialog( + onDismissRequest = { + // hide dialog + showWhitelistEnableDialog = false + }, + title = Phrase.from(context, R.string.runSessionBackground) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString(), + text = Phrase.from(context, R.string.runSessionBackgroundDescription) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString(), + buttons = listOf( + DialogButtonData( + text = GetString(getString(R.string.allow)), + qaTag = getString(R.string.qa_conversation_settings_dialog_whitelist_confirm), + onClick = { + openSystemBgWhitelist() + } + ), + DialogButtonData( + text = GetString(getString(R.string.cancel)), + qaTag = getString(R.string.qa_conversation_settings_dialog_whitelist_cancel), + ), + ) + ) + } + + if(showWhitelistDisableDialog) { + AlertDialog( + onDismissRequest = { + // hide dialog + showWhitelistDisableDialog = false + }, + title = stringResource(R.string.limitBackgroundActivity), + text = Phrase.from(context, R.string.limitBackgroundActivityDescription) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString(), + buttons = listOf( + DialogButtonData( + text = GetString("Change Setting"), + qaTag = getString(R.string.qa_conversation_settings_dialog_whitelist_confirm), + color = LocalColors.current.danger, + onClick = { + // we can't disable it ourselves, but we can take the user to the right settings instead + openBatteryOptimizationSettings() + } + ), + DialogButtonData( + text = GetString(getString(R.string.cancel)), + qaTag = getString(R.string.qa_conversation_settings_dialog_whitelist_cancel), + ), + ) + ) + } + } + } + } + override fun onCreate(paramBundle: Bundle?) { super.onCreate(paramBundle) + // whitelist control + whiteListControl = findPreference("whitelist_background")!! + whiteListControl?.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + // if already whitelisted, show toast + if(requireContext().isWhitelistedFromDoze()){ + showWhitelistDisableDialog = true + } else { + openSystemBgWhitelist() + } + true + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + prefs.pushEnabled.collect { enabled -> + whiteListControl?.isVisible = !enabled + } + } + } // Set up FCM toggle val fcmKey = "pref_key_use_fcm" @@ -36,6 +192,11 @@ class NotificationsPreferenceFragment : CorrectedPreferenceFragment() { fcmPreference.isChecked = prefs.pushEnabled.value fcmPreference.setOnPreferenceChangeListener { _: Preference, newValue: Any -> prefs.setPushEnabled(newValue as Boolean) + // open whitelist dialog when setting to slow mode if first time + if(!newValue && !prefs.hasCheckedDozeWhitelist()){ + showWhitelistEnableDialog = true + prefs.setHasCheckedDozeWhitelist(true) + } true } @@ -86,7 +247,7 @@ class NotificationsPreferenceFragment : CorrectedPreferenceFragment() { true } - findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)!!.onPreferenceClickListener = + findPreference("system_notifications")!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) intent.putExtra( @@ -101,6 +262,33 @@ class NotificationsPreferenceFragment : CorrectedPreferenceFragment() { initializeMessageVibrateSummary(findPreference(TextSecurePreferences.VIBRATE_PREF) as SwitchPreferenceCompat?) } + override fun onResume() { + super.onResume() + + whiteListControl?.isChecked = requireContext().isWhitelistedFromDoze() + } + + // Opens the system Battery Optimization settings + private fun openBatteryOptimizationSettings() { + try { + val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).apply { + data = Uri.parse("package:${requireContext().packageName}") + } + + startActivity(intent) + } catch (e: ActivityNotFoundException) { + // Fallback: open the generic Battery Optimization settings screen + val fallbackIntent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + startActivity(fallbackIntent) + } + } + + private fun openSystemBgWhitelist(){ + requireActivity().requestDozeWhitelist() + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_notifications) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt index 95da4c7df5..aa3e8a80b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Util.kt @@ -1,10 +1,13 @@ package org.thoughtcrime.securesms.ui import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.net.Uri +import android.os.PowerManager +import android.provider.Settings import android.view.View import android.view.ViewTreeObserver import android.widget.Toast @@ -89,6 +92,34 @@ fun Context.findActivity(): Activity { throw IllegalStateException("Permissions should be called in the context of an Activity") } +fun Context.isWhitelistedFromDoze(): Boolean { + val pm = getSystemService(Context.POWER_SERVICE) as PowerManager + return pm.isIgnoringBatteryOptimizations(packageName) +} + +fun Activity.requestDozeWhitelist() { + if (isWhitelistedFromDoze()) return + + val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { + data = Uri.parse("package:$packageName") + } + try { + startActivity(intent) // shows the system dialog for this specific app + } catch (_: ActivityNotFoundException) { + // Fallback to the general settings list + try { + val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS) + startActivity(intent) + } catch (_: ActivityNotFoundException) { + try { + startActivity(Intent(Settings.ACTION_SETTINGS)) + } catch (_: ActivityNotFoundException) { + Toast.makeText(this, R.string.errorGeneric, Toast.LENGTH_LONG).show() + } + } + } +} + inline fun T.afterMeasured(crossinline block: T.() -> Unit) { viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { diff --git a/app/src/main/res/xml/preferences_notifications.xml b/app/src/main/res/xml/preferences_notifications.xml index 076149f257..2f3e0b7377 100644 --- a/app/src/main/res/xml/preferences_notifications.xml +++ b/app/src/main/res/xml/preferences_notifications.xml @@ -1,6 +1,7 @@ - + @@ -10,8 +11,14 @@ android:summary="@string/notificationsFastModeDescription" android:defaultValue="false" /> + + + android:key="system_notifications" /> diff --git a/content-descriptions/src/main/res/values/strings.xml b/content-descriptions/src/main/res/values/strings.xml index c977a454df..d8a7e4c693 100644 --- a/content-descriptions/src/main/res/values/strings.xml +++ b/content-descriptions/src/main/res/values/strings.xml @@ -136,6 +136,8 @@ hide-nts-cancel-button show-nts-confirm-button show-nts-cancel-button + whitelist-confirm-button + whitelist-cancel-button block-user-confirm-button block-user-cancel-button unblock-user-confirm-button