From 9543af4784befaa453120d4a102cedc59d52ae93 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 9 Aug 2023 21:56:53 +0300 Subject: [PATCH] android: workaround of system restricted background (#2873) * android: workaround of system restricted background * strings * exclude lockscreen call from requirements to unrestrict * texts * added button and changed behaviour * texts 2 * button instead of alert * padding * bigger padding * refactor * don't jump on button hide * no exceptions for lockscreen * sometimes do not show off alert --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../main/java/chat/simplex/app/SimplexApp.kt | 21 ++- .../java/chat/simplex/app/SimplexService.kt | 165 ++++++++++++++++-- .../common/views/call/CallView.android.kt | 38 +++- .../chat/simplex/common/platform/Platform.kt | 2 + .../simplex/common/views/call/CallManager.kt | 4 +- .../simplex/common/views/chat/ChatView.kt | 12 +- .../common/views/usersettings/SettingsView.kt | 5 +- .../commonMain/resources/MR/base/strings.xml | 10 +- .../resources/MR/images/ic_bolt_off.svg | 60 +++++++ 9 files changed, 282 insertions(+), 35 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 208beff21..2cb92cb4b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -93,12 +93,12 @@ class SimplexApp: Application(), LifecycleEventObserver { fun allowToStartServiceAfterAppExit() = with(chatModel.controller) { appPrefs.notificationsMode.get() == NotificationsMode.SERVICE && - (!NotificationsMode.SERVICE.requiresIgnoringBattery || SimplexService.isIgnoringBatteryOptimizations()) + (!NotificationsMode.SERVICE.requiresIgnoringBattery || SimplexService.isBackgroundAllowed()) } private fun allowToStartPeriodically() = with(chatModel.controller) { appPrefs.notificationsMode.get() == NotificationsMode.PERIODIC && - (!NotificationsMode.PERIODIC.requiresIgnoringBattery || SimplexService.isIgnoringBatteryOptimizations()) + (!NotificationsMode.PERIODIC.requiresIgnoringBattery || SimplexService.isBackgroundAllowed()) } /* @@ -158,7 +158,7 @@ class SimplexApp: Application(), LifecycleEventObserver { } override fun androidNotificationsModeChanged(mode: NotificationsMode) { - if (mode.requiresIgnoringBattery && !SimplexService.isIgnoringBatteryOptimizations()) { + if (mode.requiresIgnoringBattery && !SimplexService.isBackgroundAllowed()) { appPrefs.backgroundServiceNoticeShown.set(false) } SimplexService.StartReceiver.toggleReceiver(mode == NotificationsMode.SERVICE) @@ -172,7 +172,7 @@ class SimplexApp: Application(), LifecycleEventObserver { if (mode != NotificationsMode.PERIODIC) { MessagesFetcherWorker.cancelAll() } - SimplexService.showBackgroundServiceNoticeIfNeeded() + SimplexService.showBackgroundServiceNoticeIfNeeded(showOffAlert = false) } override fun androidChatStartedAfterBeingOff() { @@ -199,6 +199,19 @@ class SimplexApp: Application(), LifecycleEventObserver { } } } + + override fun androidIsBackgroundCallAllowed(): Boolean = !SimplexService.isBackgroundRestricted() + + override suspend fun androidAskToAllowBackgroundCalls(): Boolean { + if (SimplexService.isBackgroundRestricted()) { + val userChoice: CompletableDeferred = CompletableDeferred() + SimplexService.showBGRestrictedInCall { + userChoice.complete(it) + } + return userChoice.await() + } + return true + } } } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index 8624daa5e..2cb6c12da 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -9,6 +9,7 @@ import android.os.* import android.provider.Settings import androidx.compose.foundation.layout.* import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -17,7 +18,6 @@ import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.work.* import chat.simplex.common.AppLock -import chat.simplex.common.AppLock.clearAuthState import chat.simplex.common.helpers.requiresIgnoringBattery import chat.simplex.common.model.ChatController import chat.simplex.common.model.NotificationsMode @@ -50,6 +50,7 @@ class SimplexService: Service() { } else { Log.d(TAG, "null intent. Probably restarted by the system.") } + startForeground(SIMPLEX_SERVICE_ID, serviceNotification) return START_STICKY // to restart if killed } @@ -353,7 +354,7 @@ class SimplexService: Service() { private fun getPreferences(context: Context): SharedPreferences = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) - fun showBackgroundServiceNoticeIfNeeded() { + fun showBackgroundServiceNoticeIfNeeded(showOffAlert: Boolean = true) { val appPrefs = ChatController.appPrefs val mode = appPrefs.notificationsMode.get() Log.d(TAG, "showBackgroundServiceNoticeIfNeeded") @@ -362,27 +363,28 @@ class SimplexService: Service() { if (!appPrefs.backgroundServiceNoticeShown.get()) { // the branch for the new users who have never seen service notice - if (!mode.requiresIgnoringBattery || isIgnoringBatteryOptimizations()) { + if (!mode.requiresIgnoringBattery || isBackgroundAllowed()) { showBGServiceNotice(mode) - } else { - showBGServiceNoticeIgnoreOptimization(mode) + } else if (isBackgroundRestricted()) { + showBGServiceNoticeSystemRestricted(mode, showOffAlert) + } else if (!isIgnoringBatteryOptimizations()) { + showBGServiceNoticeIgnoreOptimization(mode, showOffAlert) } // set both flags, so that if the user doesn't allow ignoring optimizations, the service will be disabled without additional notice appPrefs.backgroundServiceNoticeShown.set(true) appPrefs.backgroundServiceBatteryNoticeShown.set(true) + } else if (mode.requiresIgnoringBattery && isBackgroundRestricted()) { + // the branch for users who have app installed, and have seen the service notice, + // but the service is running AND system background restriction is on OR the battery optimization for the app is in RESTRICTED state + showBGServiceNoticeSystemRestricted(mode, showOffAlert) + if (!appPrefs.backgroundServiceBatteryNoticeShown.get()) { + appPrefs.backgroundServiceBatteryNoticeShown.set(true) + } } else if (mode.requiresIgnoringBattery && !isIgnoringBatteryOptimizations()) { // the branch for users who have app installed, and have seen the service notice, - // but the battery optimization for the app is on (Android 12) AND the service is running - if (appPrefs.backgroundServiceBatteryNoticeShown.get()) { - // users have been presented with battery notice before - they did not allow ignoring optimizations -> disable service - showDisablingServiceNotice(mode) - appPrefs.notificationsMode.set(NotificationsMode.OFF) - StartReceiver.toggleReceiver(false) - MessagesFetcherWorker.cancelAll() - safeStopService() - } else { - // show battery optimization notice - showBGServiceNoticeIgnoreOptimization(mode) + // but the battery optimization for the app is in OPTIMIZED state (Android 12+) AND the service is running + showBGServiceNoticeIgnoreOptimization(mode, showOffAlert) + if (!appPrefs.backgroundServiceBatteryNoticeShown.get()) { appPrefs.backgroundServiceBatteryNoticeShown.set(true) } } else { @@ -425,13 +427,17 @@ class SimplexService: Service() { ) } - private fun showBGServiceNoticeIgnoreOptimization(mode: NotificationsMode) = AlertManager.shared.showAlert { + private fun showBGServiceNoticeIgnoreOptimization(mode: NotificationsMode, showOffAlert: Boolean) = AlertManager.shared.showAlert { val ignoreOptimization = { AlertManager.shared.hideAlert() askAboutIgnoringBatteryOptimization() } + val disableNotifications = { + AlertManager.shared.hideAlert() + disableNotifications(mode, showOffAlert) + } AlertDialog( - onDismissRequest = ignoreOptimization, + onDismissRequest = disableNotifications, title = { Row { Icon( @@ -454,12 +460,98 @@ class SimplexService: Service() { Text(annotatedStringResource(MR.strings.turn_off_battery_optimization)) } }, + dismissButton = { + TextButton(onClick = disableNotifications) { Text(stringResource(MR.strings.disable_notifications_button), color = MaterialTheme.colors.error) } + }, confirmButton = { - TextButton(onClick = ignoreOptimization) { Text(stringResource(MR.strings.ok)) } + TextButton(onClick = ignoreOptimization) { Text(stringResource(MR.strings.turn_off_battery_optimization_button)) } } ) } + private fun showBGServiceNoticeSystemRestricted(mode: NotificationsMode, showOffAlert: Boolean) = AlertManager.shared.showAlert { + val unrestrict = { + AlertManager.shared.hideAlert() + askToUnrestrictBackground() + } + val disableNotifications = { + AlertManager.shared.hideAlert() + disableNotifications(mode, showOffAlert) + } + AlertDialog( + onDismissRequest = disableNotifications, + title = { + Row { + Icon( + painterResource(MR.images.ic_bolt), + contentDescription = + if (mode == NotificationsMode.SERVICE) stringResource(MR.strings.icon_descr_instant_notifications) else stringResource(MR.strings.periodic_notifications), + ) + Text( + if (mode == NotificationsMode.SERVICE) stringResource(MR.strings.service_notifications) else stringResource(MR.strings.periodic_notifications), + fontWeight = FontWeight.Bold + ) + } + }, + text = { + Column { + Text( + annotatedStringResource(MR.strings.system_restricted_background_desc), + Modifier.padding(bottom = 8.dp) + ) + Text(annotatedStringResource(MR.strings.system_restricted_background_warn)) + } + }, + dismissButton = { + TextButton(onClick = disableNotifications) { Text(stringResource(MR.strings.disable_notifications_button), color = MaterialTheme.colors.error) } + }, + confirmButton = { + TextButton(onClick = unrestrict) { Text(stringResource(MR.strings.turn_off_system_restriction_button)) } + } + ) + } + + fun showBGRestrictedInCall(onDismiss: (allowedCall: Boolean) -> Unit) = AlertManager.shared.showAlert { + val unrestrict = { + askToUnrestrictBackground() + } + AlertDialog( + onDismissRequest = AlertManager.shared::hideAlert, + title = { + Text( + stringResource(MR.strings.system_restricted_background_in_call_title), + fontWeight = FontWeight.Bold + ) + }, + text = { + Column { + Text( + annotatedStringResource(MR.strings.system_restricted_background_in_call_desc), + Modifier.padding(bottom = 8.dp) + ) + Text(annotatedStringResource(MR.strings.system_restricted_background_in_call_warn)) + } + }, + confirmButton = { + TextButton(onClick = unrestrict) { Text(stringResource(MR.strings.turn_off_system_restriction_button)) } + } + ) + val scope = rememberCoroutineScope() + DisposableEffect(Unit) { + scope.launch { + repeat(10000) { + delay(200) + if (!isBackgroundRestricted()) { + AlertManager.shared.hideAlert() + } + } + } + onDispose { + onDismiss(!isBackgroundRestricted()) + } + } + } + private fun showDisablingServiceNotice(mode: NotificationsMode) = AlertManager.shared.showAlert { AlertDialog( onDismissRequest = AlertManager.shared::hideAlert, @@ -490,11 +582,22 @@ class SimplexService: Service() { ) } + fun isBackgroundAllowed(): Boolean = isIgnoringBatteryOptimizations() && !isBackgroundRestricted() + fun isIgnoringBatteryOptimizations(): Boolean { val powerManager = androidAppContext.getSystemService(Application.POWER_SERVICE) as PowerManager return powerManager.isIgnoringBatteryOptimizations(androidAppContext.packageName) } + fun isBackgroundRestricted(): Boolean { + return if (Build.VERSION.SDK_INT >= 28) { + val activityService = androidAppContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager + activityService.isBackgroundRestricted + } else { + false + } + } + private fun askAboutIgnoringBatteryOptimization() { Intent().apply { @SuppressLint("BatteryLife") @@ -505,5 +608,29 @@ class SimplexService: Service() { androidAppContext.startActivity(this) } } + + private fun askToUnrestrictBackground() { + Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.parse("package:${androidAppContext.packageName}") + // This flag is needed when you start a new activity from non-Activity context + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + try { + androidAppContext.startActivity(this) + } catch (e: ActivityNotFoundException) { + Log.e(TAG, e.stackTraceToString()) + } + } + } + + private fun disableNotifications(mode: NotificationsMode, showOffAlert: Boolean) { + if (showOffAlert) { + showDisablingServiceNotice(mode) + } + ChatController.appPrefs.notificationsMode.set(NotificationsMode.OFF) + StartReceiver.toggleReceiver(false) + MessagesFetcherWorker.cancelAll() + safeStopService() + } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index a549d985a..260182b5a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -12,7 +12,9 @@ import android.os.PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK import android.view.ViewGroup import android.webkit.* import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -33,11 +35,10 @@ import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewClientCompat import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.ProfileImage -import chat.simplex.common.views.helpers.withApi import chat.simplex.common.model.ChatModel import chat.simplex.common.model.Contact import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import com.google.accompanist.permissions.rememberMultiplePermissionsState import dev.icerock.moko.resources.StringResource @@ -267,7 +268,9 @@ private fun ActiveCallOverlayLayout( when (call.peerMedia ?: call.localMedia) { CallMediaType.Video -> { CallInfoView(call, alignment = Alignment.Start) - Spacer(Modifier.fillMaxHeight().weight(1f)) + Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { + DisabledBackgroundCallsButton() + } Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { ToggleAudioButton(call, toggleAudio) Spacer(Modifier.size(40.dp)) @@ -293,7 +296,9 @@ private fun ActiveCallOverlayLayout( ProfileImage(size = 192.dp, image = call.contact.profile.image) CallInfoView(call, alignment = Alignment.CenterHorizontally) } - Spacer(Modifier.fillMaxHeight().weight(1f)) + Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { + DisabledBackgroundCallsButton() + } Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) { Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { IconButton(onClick = dismiss) { @@ -358,6 +363,31 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) { } } +@Composable +private fun DisabledBackgroundCallsButton() { + var show by remember { mutableStateOf(!platform.androidIsBackgroundCallAllowed()) } + if (show) { + Row( + Modifier + .padding(bottom = 24.dp) + .clickable { + withBGApi { + show = !platform.androidAskToAllowBackgroundCalls() + } + } + .background(WarningOrange.copy(0.3f), RoundedCornerShape(50)) + .padding(start = 14.dp, top = 4.dp, end = 8.dp, bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(stringResource(MR.strings.system_restricted_background_in_call_title), color = WarningOrange) + Spacer(Modifier.width(8.dp)) + IconButton(onClick = { show = false }, Modifier.size(24.dp)) { + Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange) + } + } + } +} + //@Composable //fun CallViewDebug(close: () -> Unit) { // val callCommand = remember { mutableStateOf(null)} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index eeea2d08f..84ffdb6fd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -9,6 +9,8 @@ interface PlatformInterface { fun androidChatStartedAfterBeingOff() {} fun androidChatStopped() {} fun androidChatInitializedAndStarted() {} + fun androidIsBackgroundCallAllowed(): Boolean = true + suspend fun androidAskToAllowBackgroundCalls(): Boolean = true } /** * Multiplatform project has separate directories per platform + common directory that contains directories per platform + common for all of them. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index 1a2f5df57..fe4da718a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -3,6 +3,7 @@ package chat.simplex.common.views.call import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.withApi +import chat.simplex.common.views.helpers.withBGApi import chat.simplex.common.views.usersettings.showInDevelopingAlert import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes @@ -28,6 +29,7 @@ class CallManager(val chatModel: ChatModel) { if (appPlatform.isDesktop) { return showInDevelopingAlert() } + val call = chatModel.activeCall.value if (call == null) { justAcceptIncomingCall(invitation = invitation) @@ -50,7 +52,7 @@ class CallManager(val chatModel: ChatModel) { contact = invitation.contact, callState = CallState.InvitationAccepted, localMedia = invitation.callType.media, - sharedKey = invitation.sharedKey + sharedKey = invitation.sharedKey, ) showCallView.value = true val useRelay = controller.appPrefs.webrtcPolicyRelay.get() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 63cf729ed..70825e6fb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -239,11 +239,13 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { if (appPlatform.isDesktop) { return@out showInDevelopingAlert() } - val cInfo = chat.chatInfo - if (cInfo is ChatInfo.Direct) { - chatModel.activeCall.value = Call(contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) - chatModel.showCallView.value = true - chatModel.callCommand.value = WCallCommand.Capabilities + withBGApi { + val cInfo = chat.chatInfo + if (cInfo is ChatInfo.Direct) { + chatModel.activeCall.value = Call(contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) + chatModel.showCallView.value = true + chatModel.callCommand.value = WCallCommand.Capabilities + } } }, acceptCall = { contact -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index e81746f99..ebd0cf9b0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -43,6 +43,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerSt profile = user.profile, stopped, chatModel.chatDbEncrypted.value == true, + remember { chatModel.controller.appPrefs.notificationsMode.state }, user.displayName, setPerformLA = setPerformLA, showModal = { modalView -> { ModalManager.start.showModal { modalView(chatModel) } } }, @@ -114,6 +115,7 @@ fun SettingsLayout( profile: LocalProfile, stopped: Boolean, encrypted: Boolean, + notificationsMode: State, userDisplayName: String, setPerformLA: (Boolean) -> Unit, showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), @@ -155,7 +157,7 @@ fun SettingsLayout( SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_settings)) { - SettingsActionItem(painterResource(MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal, showCustomModal) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true) @@ -464,6 +466,7 @@ fun PreviewSettingsLayout() { profile = LocalProfile.sampleData, stopped = false, encrypted = false, + notificationsMode = remember { mutableStateOf(NotificationsMode.OFF) }, userDisplayName = "Alice", setPerformLA = { _ -> }, showModal = { {} }, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index c060470b1..42a2927a9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -132,11 +132,19 @@ Instant notifications are disabled! SimpleX background service – it uses a few percent of the battery per day.]]> It can be disabled via settings – notifications will still be shown while the app is running.]]> - disable battery optimization for SimpleX in the next dialog. Otherwise, the notifications will be disabled.]]> + allow SimpleX to run in background in the next dialog. Otherwise, the notifications will be disabled.]]> Battery optimization is active, turning off background service and periodic requests for new messages. You can re-enable them via settings. Periodic notifications Periodic notifications are disabled! The app fetches new messages periodically — it uses a few percent of the battery per day. The app doesn\'t use push notifications — data from your device is not sent to the servers. + Allow + Open app settings + Disable notifications + + App battery usage / Unrestricted in the app settings.]]> + No background calls + + App battery usage / Unrestricted in the app settings]]>. Passphrase is needed To receive notifications, please, enter the database passphrase Can\'t initialize the database diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg new file mode 100644 index 000000000..7b003c8e7 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bolt_off.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + +