From ff7c22e114c5e6417a0a48f1f5511eab4800abed Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 7 Jul 2023 13:44:22 +0300 Subject: [PATCH 01/18] multiplatform: divided files in pieces (#2664) * multiplatform: divided files in pieces * whitespace * whitespace 2 --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../src/main/java/chat/simplex/app/App.kt | 213 +++++ .../src/main/java/chat/simplex/app/AppLock.kt | 252 ++++++ .../java/chat/simplex/app/MainActivity.kt | 506 +---------- .../main/java/chat/simplex/app/SimplexApp.kt | 138 +-- .../java/chat/simplex/app/SimplexService.kt | 8 +- .../chat/simplex/app/helpers/Extensions.kt | 39 + .../java/chat/simplex/app/model/ChatModel.kt | 1 - .../java/chat/simplex/app/model/NtfManager.kt | 6 +- .../java/chat/simplex/app/model/SimpleXAPI.kt | 15 +- .../simplex/app/platform/AppCommon.common.kt | 30 + .../chat/simplex/app/platform/AppCommon.kt | 61 ++ .../simplex/app/platform/Backend.common.kt | 76 ++ .../chat/simplex/app/platform/Files.common.kt | 30 + .../java/chat/simplex/app/platform/Images.kt | 106 +++ .../simplex/app/platform/PlatformTextField.kt | 158 ++++ .../chat/simplex/app/platform/Resources.kt | 9 + .../app/{views/helpers => platform}/Share.kt | 30 +- .../main/java/chat/simplex/app/platform/UI.kt | 61 ++ .../java/chat/simplex/app/ui/theme/Theme.kt | 8 +- .../chat/simplex/app/ui/theme/Type.common.kt | 50 ++ .../java/chat/simplex/app/ui/theme/Type.kt | 47 - .../chat/simplex/app/views/TerminalView.kt | 1 + .../simplex/app/views/call/CallManager.kt | 1 - .../chat/simplex/app/views/call/CallView.kt | 2 - .../chat/simplex/app/views/call/WebRTC.kt | 6 +- .../simplex/app/views/chat/ChatInfoView.kt | 2 +- .../app/views/chat/ChatItemInfoView.kt | 3 +- .../chat/simplex/app/views/chat/ChatView.kt | 2 +- .../app/views/chat/ComposeImageView.kt | 2 +- .../app/views/chat/ComposeView.common.kt | 759 ++++++++++++++++ .../simplex/app/views/chat/ComposeView.kt | 833 +----------------- .../app/views/chat/ScanCodeView.common.kt | 41 + .../simplex/app/views/chat/ScanCodeView.kt | 39 - .../app/views/chat/SendMsgView.common.kt | 653 ++++++++++++++ .../simplex/app/views/chat/SendMsgView.kt | 811 +---------------- .../simplex/app/views/chat/VerifyCodeView.kt | 2 +- .../views/chat/group/AddGroupMembersView.kt | 3 +- .../app/views/chat/group/GroupLinkView.kt | 2 +- .../views/chat/group/GroupMemberInfoView.kt | 2 +- .../app/views/chat/group/GroupProfileView.kt | 2 + .../views/chat/group/WelcomeMessageView.kt | 3 +- .../simplex/app/views/chat/item/CIFileView.kt | 33 +- .../app/views/chat/item/CIImageView.common.kt | 188 ++++ .../app/views/chat/item/CIImageView.kt | 205 +---- .../app/views/chat/item/CIInvalidJSONView.kt | 2 +- .../app/views/chat/item/CIVIdeoView.common.kt | 318 +++++++ .../app/views/chat/item/CIVideoView.kt | 324 +------ .../app/views/chat/item/CIVoiceView.kt | 1 + .../views/chat/item/ChatItemView.common.kt | 533 +++++++++++ .../app/views/chat/item/ChatItemView.kt | 559 +----------- .../app/views/chat/item/FramedItemView.kt | 1 + .../chat/item/ImageFullScreenView.common.kt | 178 ++++ .../views/chat/item/ImageFullScreenView.kt | 235 +---- .../app/views/chatlist/ChatListView.kt | 33 +- .../app/views/database/ChatArchiveView.kt | 3 +- .../app/views/database/DatabaseErrorView.kt | 5 +- .../app/views/database/DatabaseView.kt | 6 +- .../app/views/helpers/ChatInfoImage.kt | 1 + ...nMenu.kt => DefaultDropdownMenu.common.kt} | 2 - .../simplex/app/views/helpers/GetImageView.kt | 134 ++- .../simplex/app/views/helpers/LinkPreviews.kt | 2 + .../helpers/LocalAuthentication.common.kt | 49 ++ .../app/views/helpers/LocalAuthentication.kt | 72 +- .../views/helpers/MessagesFetcherWorker.kt | 3 +- .../simplex/app/views/helpers/TextEditor.kt | 2 +- .../chat/simplex/app/views/helpers/Util.kt | 381 +------- .../simplex/app/views/helpers/Utils.common.kt | 296 +++++++ .../app/views/localauth/LocalAuthView.kt | 3 +- .../app/views/newchat/AddContactView.kt | 2 +- .../simplex/app/views/newchat/AddGroupView.kt | 2 + .../newchat/ContactConnectionInfoView.kt | 2 +- .../app/views/newchat/QRCode.common.kt | 80 ++ .../chat/simplex/app/views/newchat/QRCode.kt | 89 -- .../views/newchat/ScanToConnectView.common.kt | 151 ++++ .../app/views/newchat/ScanToConnectView.kt | 147 +--- .../views/onboarding/CreateSimpleXAddress.kt | 3 +- .../app/views/onboarding/OnboardingView.kt | 2 +- .../onboarding/SetNotificationsMode.common.kt | 78 ++ .../views/onboarding/SetNotificationsMode.kt | 81 +- .../views/usersettings/Appearance.common.kt | 288 ++++++ .../app/views/usersettings/Appearance.kt | 248 +----- .../usersettings/NotificationsSettingsView.kt | 12 +- .../usersettings/PrivacySettings.common.kt | 484 ++++++++++ .../app/views/usersettings/PrivacySettings.kt | 472 +--------- .../usersettings/ScanProtocolServer.common.kt | 42 + .../views/usersettings/ScanProtocolServer.kt | 39 - .../views/usersettings/SettingsView.common.kt | 511 +++++++++++ .../app/views/usersettings/SettingsView.kt | 517 +---------- .../app/views/usersettings/UserAddressView.kt | 3 +- .../app/views/usersettings/UserProfileView.kt | 2 +- .../views/usersettings/UserProfilesView.kt | 2 +- .../src/commonMain/cpp/android/simplex-api.c | 18 +- 92 files changed, 6124 insertions(+), 5733 deletions(-) create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/App.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/AppLock.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/helpers/Extensions.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Backend.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Images.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt rename apps/multiplatform/android/src/main/java/chat/simplex/app/{views/helpers => platform}/Share.kt (76%) create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/UI.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt rename apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/{DefaultDropdownMenu.kt => DefaultDropdownMenu.common.kt} (95%) create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt create mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/App.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/App.kt new file mode 100644 index 0000000000..2079abed0c --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/App.kt @@ -0,0 +1,213 @@ +package chat.simplex.app + +import android.os.SystemClock +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.unit.dp +import chat.simplex.app.model.ChatModel +import chat.simplex.app.ui.theme.DEFAULT_PADDING +import chat.simplex.app.ui.theme.SimpleButton +import chat.simplex.app.views.SplashView +import chat.simplex.app.views.call.ActiveCallView +import chat.simplex.app.views.call.IncomingCallAlertView +import chat.simplex.app.views.chat.ChatView +import chat.simplex.app.views.chatlist.ChatListView +import chat.simplex.app.views.chatlist.ShareListView +import chat.simplex.app.views.database.DatabaseErrorView +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.onboarding.* +import chat.simplex.app.views.usersettings.LAMode +import chat.simplex.app.views.usersettings.laUnavailableInstructionAlert +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch + +@Composable +fun MainPage( + chatModel: ChatModel, + userAuthorized: MutableState, + laFailed: MutableState, + destroyedAfterBackPress: MutableState, + runAuthenticate: () -> Unit, + setPerformLA: (Boolean) -> Unit, + showLANotice: () -> Unit +) { + var showChatDatabaseError by rememberSaveable { + mutableStateOf(chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null) + } + LaunchedEffect(chatModel.chatDbStatus.value) { + showChatDatabaseError = chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null + } + + var showAdvertiseLAAlert by remember { mutableStateOf(false) } + LaunchedEffect(showAdvertiseLAAlert) { + if ( + !chatModel.controller.appPrefs.laNoticeShown.get() + && showAdvertiseLAAlert + && chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete + && chatModel.chats.isNotEmpty() + && chatModel.activeCallInvitation.value == null + ) { + showLANotice() + } + } + LaunchedEffect(chatModel.showAdvertiseLAUnavailableAlert.value) { + if (chatModel.showAdvertiseLAUnavailableAlert.value) { + laUnavailableInstructionAlert() + } + } + LaunchedEffect(chatModel.clearOverlays.value) { + if (chatModel.clearOverlays.value) { + ModalManager.shared.closeModals() + chatModel.clearOverlays.value = false + } + } + + @Composable + fun AuthView() { + Surface(color = MaterialTheme.colors.background) { + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + SimpleButton( + stringResource(MR.strings.auth_unlock), + icon = painterResource(MR.images.ic_lock), + click = { + laFailed.value = false + runAuthenticate() + } + ) + } + } + } + + Box { + val onboarding = chatModel.onboardingStage.value + val userCreated = chatModel.userCreated.value + var showInitializationView by remember { mutableStateOf(false) } + when { + chatModel.chatDbStatus.value == null && showInitializationView -> InitializationView() + showChatDatabaseError -> { + chatModel.chatDbStatus.value?.let { + DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs) + } + } + onboarding == null || userCreated == null -> SplashView() + onboarding == OnboardingStage.OnboardingComplete && userCreated -> { + Box { + showAdvertiseLAAlert = true + BoxWithConstraints { + var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) } + val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) } + Box( + Modifier + .graphicsLayer { + translationX = -offset.value.dp.toPx() + } + ) { + val stopped = chatModel.chatRunning.value == false + if (chatModel.sharedContent.value == null) + ChatListView(chatModel, setPerformLA, stopped) + else + ShareListView(chatModel, stopped) + } + val scope = rememberCoroutineScope() + val onComposed: () -> Unit = { + scope.launch { + offset.animateTo( + if (chatModel.chatId.value == null) 0f else maxWidth.value, + chatListAnimationSpec() + ) + if (offset.value == 0f) { + currentChatId = null + } + } + } + LaunchedEffect(Unit) { + launch { + snapshotFlow { chatModel.chatId.value } + .distinctUntilChanged() + .collect { + if (it != null) currentChatId = it + else onComposed() + } + } + } + Box (Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@ { + currentChatId?.let { + ChatView(it, chatModel, onComposed) + } + } + } + } + } + onboarding == OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) + onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {} + onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel) + onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) + } + ModalManager.shared.showInView() + val unauthorized = remember { derivedStateOf { userAuthorized.value != true } } + if (unauthorized.value && !(chatModel.activeCallViewIsVisible.value && chatModel.showCallView.value)) { + LaunchedEffect(Unit) { + // With these constrains when user presses back button while on ChatList, activity destroys and shows auth request + // while the screen moves to a launcher. Detect it and prevent showing the auth + if (!(destroyedAfterBackPress.value && chatModel.controller.appPrefs.laMode.get() == LAMode.SYSTEM)) { + runAuthenticate() + } + } + if (chatModel.controller.appPrefs.performLA.get() && laFailed.value) { + AuthView() + } else { + SplashView() + } + } else if (chatModel.showCallView.value) { + ActiveCallView(chatModel) + } + ModalManager.shared.showPasscodeInView() + val invitation = chatModel.activeCallInvitation.value + if (invitation != null) IncomingCallAlertView(invitation, chatModel) + AlertManager.shared.showInView() + + LaunchedEffect(Unit) { + delay(1000) + if (chatModel.chatDbStatus.value == null) { + showInitializationView = true + } + } + } + + DisposableEffectOnRotate { + // When using lock delay = 0 and screen rotates, the app will be locked which is not useful. + // Let's prolong the unlocked period to 3 sec for screen rotation to take place + if (chatModel.controller.appPrefs.laLockDelay.get() == 0) { + AppLock.enteredBackground.value = SystemClock.elapsedRealtime() + 3000 + } + } +} + +@Composable +private fun InitializationView() { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator( + Modifier + .padding(bottom = DEFAULT_PADDING) + .size(30.dp), + color = MaterialTheme.colors.secondary, + strokeWidth = 2.5.dp + ) + Text(stringResource(MR.strings.opening_database)) + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/AppLock.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/AppLock.kt new file mode 100644 index 0000000000..7cd680592f --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/AppLock.kt @@ -0,0 +1,252 @@ +package chat.simplex.app + +import android.util.Log +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Modifier +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.SharedPreference +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.localauth.SetAppPasscodeView +import chat.simplex.app.views.usersettings.* +import chat.simplex.res.MR +import kotlinx.coroutines.* + +object AppLock { + /** + * We don't want these values to be bound to Activity lifecycle since activities are changed often, for example, when a user + * clicks on new message in notification. In this case savedInstanceState will be null (this prevents restoring the values) + * See [SimplexService.onTaskRemoved] for another part of the logic which nullifies the values when app closed by the user + * */ + val userAuthorized = mutableStateOf(null) + val enteredBackground = mutableStateOf(null) + + // Remember result and show it after orientation change + val laFailed = mutableStateOf(false) + val destroyedAfterBackPress = mutableStateOf(false) + + fun clearAuthState() { + userAuthorized.value = null + enteredBackground.value = null + } + + fun showLANotice(laNoticeShown: SharedPreference) { + Log.d(TAG, "showLANotice") + if (!laNoticeShown.get()) { + laNoticeShown.set(true) + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.la_notice_title_simplex_lock), + text = generalGetString(MR.strings.la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled), + confirmText = generalGetString(MR.strings.la_notice_turn_on), + onConfirm = { + withBGApi { // to remove this call, change ordering of onConfirm call in AlertManager + showChooseLAMode(laNoticeShown) + } + } + ) + } + } + + private fun showChooseLAMode(laNoticeShown: SharedPreference) { + Log.d(TAG, "showLANotice") + laNoticeShown.set(true) + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(MR.strings.la_lock_mode), + text = null, + confirmText = generalGetString(MR.strings.la_lock_mode_passcode), + dismissText = generalGetString(MR.strings.la_lock_mode_system), + onConfirm = { + AlertManager.shared.hideAlert() + setPasscode() + }, + onDismiss = { + AlertManager.shared.hideAlert() + initialEnableLA() + } + ) + } + + private fun initialEnableLA() { + val m = ChatModel + val appPrefs = m.controller.appPrefs + m.controller.appPrefs.laMode.set(LAMode.SYSTEM) + authenticate( + generalGetString(MR.strings.auth_enable_simplex_lock), + generalGetString(MR.strings.auth_confirm_credential), + completed = { laResult -> + when (laResult) { + LAResult.Success -> { + m.performLA.value = true + appPrefs.performLA.set(true) + laTurnedOnAlert() + } + is LAResult.Failed -> { /* Can be called multiple times on every failure */ } + is LAResult.Error -> { + m.performLA.value = false + appPrefs.performLA.set(false) + laFailedAlert() + } + is LAResult.Unavailable -> { + m.performLA.value = false + appPrefs.performLA.set(false) + m.showAdvertiseLAUnavailableAlert.value = true + } + } + } + ) + } + + private fun setPasscode() { + val chatModel = ChatModel + val appPrefs = chatModel.controller.appPrefs + ModalManager.shared.showCustomModal { close -> + Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + SetAppPasscodeView( + submit = { + chatModel.performLA.value = true + appPrefs.performLA.set(true) + appPrefs.laMode.set(LAMode.PASSCODE) + laTurnedOnAlert() + }, + cancel = { + chatModel.performLA.value = false + appPrefs.performLA.set(false) + laPasscodeNotSetAlert() + }, + close = close + ) + } + } + } + + fun setAuthState() { + userAuthorized.value = !ChatModel.controller.appPrefs.performLA.get() + } + + fun runAuthenticate() { + val m = ChatModel + setAuthState() + if (userAuthorized.value == false) { + // To make Main thread free in order to allow to Compose to show blank view that hiding content underneath of it faster on slow devices + CoroutineScope(Dispatchers.Default).launch { + delay(50) + withContext(Dispatchers.Main) { + authenticate( + if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) + generalGetString(MR.strings.auth_unlock) + else + generalGetString(MR.strings.la_enter_app_passcode), + if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) + generalGetString(MR.strings.auth_log_in_using_credential) + else + generalGetString(MR.strings.auth_unlock), + selfDestruct = true, + completed = { laResult -> + when (laResult) { + LAResult.Success -> + userAuthorized.value = true + is LAResult.Failed -> { /* Can be called multiple times on every failure */ } + is LAResult.Error -> { + laFailed.value = true + if (m.controller.appPrefs.laMode.get() == LAMode.PASSCODE) { + laFailedAlert() + } + } + is LAResult.Unavailable -> { + userAuthorized.value = true + m.performLA.value = false + m.controller.appPrefs.performLA.set(false) + laUnavailableTurningOffAlert() + } + } + } + ) + } + } + } + } + + fun setPerformLA(on: Boolean) { + ChatModel.controller.appPrefs.laNoticeShown.set(true) + if (on) { + enableLA() + } else { + disableLA() + } + } + + private fun enableLA() { + val m = ChatModel + authenticate( + if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) + generalGetString(MR.strings.auth_enable_simplex_lock) + else + generalGetString(MR.strings.new_passcode), + if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) + generalGetString(MR.strings.auth_confirm_credential) + else + "", + completed = { laResult -> + val prefPerformLA = m.controller.appPrefs.performLA + when (laResult) { + LAResult.Success -> { + m.performLA.value = true + prefPerformLA.set(true) + laTurnedOnAlert() + } + is LAResult.Failed -> { /* Can be called multiple times on every failure */ } + is LAResult.Error -> { + m.performLA.value = false + prefPerformLA.set(false) + laFailedAlert() + } + is LAResult.Unavailable -> { + m.performLA.value = false + prefPerformLA.set(false) + laUnavailableInstructionAlert() + } + } + } + ) + } + + private fun disableLA() { + val m = ChatModel + authenticate( + if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) + generalGetString(MR.strings.auth_disable_simplex_lock) + else + generalGetString(MR.strings.la_enter_app_passcode), + if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) + generalGetString(MR.strings.auth_confirm_credential) + else + generalGetString(MR.strings.auth_disable_simplex_lock), + completed = { laResult -> + val prefPerformLA = m.controller.appPrefs.performLA + val selfDestructPref = m.controller.appPrefs.selfDestruct + when (laResult) { + LAResult.Success -> { + m.performLA.value = false + prefPerformLA.set(false) + DatabaseUtils.ksAppPassword.remove() + selfDestructPref.set(false) + DatabaseUtils.ksSelfDestructPassword.remove() + } + is LAResult.Failed -> { /* Can be called multiple times on every failure */ } + is LAResult.Error -> { + m.performLA.value = true + prefPerformLA.set(true) + laFailedAlert() + } + is LAResult.Unavailable -> { + m.performLA.value = false + prefPerformLA.set(false) + laUnavailableTurningOffAlert() + } + } + } + ) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 1b04b1e673..76604ac13a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -13,60 +13,28 @@ import androidx.compose.animation.core.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity import androidx.lifecycle.* -import chat.simplex.app.MainActivity.Companion.enteredBackground +import chat.simplex.app.helpers.applyAppLocale import chat.simplex.app.model.* import chat.simplex.app.model.NtfManager.getUserIdFromIntent +import chat.simplex.app.platform.mainActivity import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.SplashView -import chat.simplex.app.views.call.ActiveCallView -import chat.simplex.app.views.call.IncomingCallAlertView -import chat.simplex.app.views.chat.ChatView import chat.simplex.app.views.chatlist.* -import chat.simplex.app.views.database.DatabaseErrorView import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword -import chat.simplex.app.views.helpers.DatabaseUtils.ksSelfDestructPassword -import chat.simplex.app.views.localauth.SetAppPasscodeView import chat.simplex.app.views.newchat.* import chat.simplex.app.views.onboarding.* -import chat.simplex.app.views.usersettings.LAMode +import chat.simplex.app.views.usersettings.* import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* -import kotlinx.coroutines.flow.distinctUntilChanged import java.lang.ref.WeakReference class MainActivity: FragmentActivity() { - companion object { - /** - * We don't want these values to be bound to Activity lifecycle since activities are changed often, for example, when a user - * clicks on new message in notification. In this case savedInstanceState will be null (this prevents restoring the values) - * See [SimplexService.onTaskRemoved] for another part of the logic which nullifies the values when app closed by the user - * */ - val userAuthorized = mutableStateOf(null) - val enteredBackground = mutableStateOf(null) - // Remember result and show it after orientation change - private val laFailed = mutableStateOf(false) - - fun clearAuthState() { - userAuthorized.value = null - enteredBackground.value = null - } - } private val vm by viewModels() - private val destroyedAfterBackPress = mutableStateOf(false) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - SimplexApp.context.mainActivity = WeakReference(this) + mainActivity = WeakReference(this) // testJson() val m = vm.chatModel applyAppLocale(m.controller.appPrefs.appLanguage) @@ -89,12 +57,12 @@ class MainActivity: FragmentActivity() { Surface(color = MaterialTheme.colors.background) { MainPage( m, - userAuthorized, - laFailed, - destroyedAfterBackPress, - ::runAuthenticate, - ::setPerformLA, - showLANotice = { showLANotice(m.controller.appPrefs.laNoticeShown) } + AppLock.userAuthorized, + AppLock.laFailed, + AppLock.destroyedAfterBackPress, + { AppLock.runAuthenticate() }, + { AppLock.setPerformLA(it) }, + showLANotice = { AppLock.showLANotice(m.controller.appPrefs.laNoticeShown) } ) } } @@ -111,14 +79,14 @@ class MainActivity: FragmentActivity() { override fun onResume() { super.onResume() - val enteredBackgroundVal = enteredBackground.value + val enteredBackgroundVal = AppLock.enteredBackground.value val delay = vm.chatModel.controller.appPrefs.laLockDelay.get() if (enteredBackgroundVal == null || elapsedRealtime() - enteredBackgroundVal >= delay * 1000) { - if (userAuthorized.value != false) { + if (AppLock.userAuthorized.value != false) { /** [runAuthenticate] will be called in [MainPage] if needed. Making like this prevents double showing of passcode on start */ - setAuthState() + AppLock.setAuthState() } else if (!vm.chatModel.activeCallViewIsVisible.value) { - runAuthenticate() + AppLock.runAuthenticate() } } } @@ -126,17 +94,17 @@ class MainActivity: FragmentActivity() { override fun onPause() { super.onPause() /** - * When new activity is created after a click on notification, the old one receives onPause before - * recreation but receives onStop after recreation. So using both (onPause and onStop) to prevent - * unwanted multiple auth dialogs from [runAuthenticate] - * */ - enteredBackground.value = elapsedRealtime() + * When new activity is created after a click on notification, the old one receives onPause before + * recreation but receives onStop after recreation. So using both (onPause and onStop) to prevent + * unwanted multiple auth dialogs from [runAuthenticate] + * */ + AppLock.enteredBackground.value = elapsedRealtime() } override fun onStop() { super.onStop() VideoPlayer.stopAll() - enteredBackground.value = elapsedRealtime() + AppLock.enteredBackground.value = elapsedRealtime() } override fun onBackPressed() { @@ -151,233 +119,15 @@ class MainActivity: FragmentActivity() { if (!onBackPressedDispatcher.hasEnabledCallbacks() && vm.chatModel.controller.appPrefs.performLA.get()) { // When pressed Back and there is no one wants to process the back event, clear auth state to force re-auth on launch - clearAuthState() - laFailed.value = true - destroyedAfterBackPress.value = true + AppLock.clearAuthState() + AppLock.laFailed.value = true + AppLock.destroyedAfterBackPress.value = true } if (!onBackPressedDispatcher.hasEnabledCallbacks()) { // Drop shared content SimplexApp.context.chatModel.sharedContent.value = null } } - - private fun setAuthState() { - userAuthorized.value = !vm.chatModel.controller.appPrefs.performLA.get() - } - - private fun runAuthenticate() { - val m = vm.chatModel - setAuthState() - if (userAuthorized.value == false) { - // To make Main thread free in order to allow to Compose to show blank view that hiding content underneath of it faster on slow devices - CoroutineScope(Dispatchers.Default).launch { - delay(50) - withContext(Dispatchers.Main) { - authenticate( - if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) - generalGetString(MR.strings.auth_unlock) - else - generalGetString(MR.strings.la_enter_app_passcode), - if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) - generalGetString(MR.strings.auth_log_in_using_credential) - else - generalGetString(MR.strings.auth_unlock), - selfDestruct = true, - completed = { laResult -> - when (laResult) { - LAResult.Success -> - userAuthorized.value = true - is LAResult.Failed -> { /* Can be called multiple times on every failure */ } - is LAResult.Error -> { - laFailed.value = true - if (m.controller.appPrefs.laMode.get() == LAMode.PASSCODE) { - laFailedAlert() - } - } - is LAResult.Unavailable -> { - userAuthorized.value = true - m.performLA.value = false - m.controller.appPrefs.performLA.set(false) - laUnavailableTurningOffAlert() - } - } - } - ) - } - } - } - } - - private fun showLANotice(laNoticeShown: SharedPreference) { - Log.d(TAG, "showLANotice") - if (!laNoticeShown.get()) { - laNoticeShown.set(true) - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.la_notice_title_simplex_lock), - text = generalGetString(MR.strings.la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled), - confirmText = generalGetString(MR.strings.la_notice_turn_on), - onConfirm = { - withBGApi { // to remove this call, change ordering of onConfirm call in AlertManager - showChooseLAMode(laNoticeShown) - } - } - ) - } - } - - private fun showChooseLAMode(laNoticeShown: SharedPreference) { - Log.d(TAG, "showLANotice") - laNoticeShown.set(true) - AlertManager.shared.showAlertDialogStacked( - title = generalGetString(MR.strings.la_lock_mode), - text = null, - confirmText = generalGetString(MR.strings.la_lock_mode_passcode), - dismissText = generalGetString(MR.strings.la_lock_mode_system), - onConfirm = { - AlertManager.shared.hideAlert() - setPasscode() - }, - onDismiss = { - AlertManager.shared.hideAlert() - initialEnableLA() - } - ) - } - - private fun initialEnableLA() { - val m = vm.chatModel - val appPrefs = m.controller.appPrefs - m.controller.appPrefs.laMode.set(LAMode.SYSTEM) - authenticate( - generalGetString(MR.strings.auth_enable_simplex_lock), - generalGetString(MR.strings.auth_confirm_credential), - completed = { laResult -> - when (laResult) { - LAResult.Success -> { - m.performLA.value = true - appPrefs.performLA.set(true) - laTurnedOnAlert() - } - is LAResult.Failed -> { /* Can be called multiple times on every failure */ } - is LAResult.Error -> { - m.performLA.value = false - appPrefs.performLA.set(false) - laFailedAlert() - } - is LAResult.Unavailable -> { - m.performLA.value = false - appPrefs.performLA.set(false) - m.showAdvertiseLAUnavailableAlert.value = true - } - } - } - ) - } - - private fun setPasscode() { - val chatModel = vm.chatModel - val appPrefs = chatModel.controller.appPrefs - ModalManager.shared.showCustomModal { close -> - Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - SetAppPasscodeView( - submit = { - chatModel.performLA.value = true - appPrefs.performLA.set(true) - appPrefs.laMode.set(LAMode.PASSCODE) - laTurnedOnAlert() - }, - cancel = { - chatModel.performLA.value = false - appPrefs.performLA.set(false) - laPasscodeNotSetAlert() - }, - close = close - ) - } - } - } - - private fun setPerformLA(on: Boolean) { - vm.chatModel.controller.appPrefs.laNoticeShown.set(true) - if (on) { - enableLA() - } else { - disableLA() - } - } - - private fun enableLA() { - val m = vm.chatModel - authenticate( - if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) - generalGetString(MR.strings.auth_enable_simplex_lock) - else - generalGetString(MR.strings.new_passcode), - if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) - generalGetString(MR.strings.auth_confirm_credential) - else - "", - completed = { laResult -> - val prefPerformLA = m.controller.appPrefs.performLA - when (laResult) { - LAResult.Success -> { - m.performLA.value = true - prefPerformLA.set(true) - laTurnedOnAlert() - } - is LAResult.Failed -> { /* Can be called multiple times on every failure */ } - is LAResult.Error -> { - m.performLA.value = false - prefPerformLA.set(false) - laFailedAlert() - } - is LAResult.Unavailable -> { - m.performLA.value = false - prefPerformLA.set(false) - laUnavailableInstructionAlert() - } - } - } - ) - } - - private fun disableLA() { - val m = vm.chatModel - authenticate( - if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) - generalGetString(MR.strings.auth_disable_simplex_lock) - else - generalGetString(MR.strings.la_enter_app_passcode), - if (m.controller.appPrefs.laMode.get() == LAMode.SYSTEM) - generalGetString(MR.strings.auth_confirm_credential) - else - generalGetString(MR.strings.auth_disable_simplex_lock), - completed = { laResult -> - val prefPerformLA = m.controller.appPrefs.performLA - val selfDestructPref = m.controller.appPrefs.selfDestruct - when (laResult) { - LAResult.Success -> { - m.performLA.value = false - prefPerformLA.set(false) - ksAppPassword.remove() - selfDestructPref.set(false) - ksSelfDestructPassword.remove() - } - is LAResult.Failed -> { /* Can be called multiple times on every failure */ } - is LAResult.Error -> { - m.performLA.value = true - prefPerformLA.set(true) - laFailedAlert() - } - is LAResult.Unavailable -> { - m.performLA.value = false - prefPerformLA.set(false) - laUnavailableTurningOffAlert() - } - } - } - ) - } } class SimplexViewModel(application: Application): AndroidViewModel(application) { @@ -385,187 +135,6 @@ class SimplexViewModel(application: Application): AndroidViewModel(application) val chatModel = app.chatModel } -@Composable -fun MainPage( - chatModel: ChatModel, - userAuthorized: MutableState, - laFailed: MutableState, - destroyedAfterBackPress: MutableState, - runAuthenticate: () -> Unit, - setPerformLA: (Boolean) -> Unit, - showLANotice: () -> Unit -) { - var showChatDatabaseError by rememberSaveable { - mutableStateOf(chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null) - } - LaunchedEffect(chatModel.chatDbStatus.value) { - showChatDatabaseError = chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null - } - - var showAdvertiseLAAlert by remember { mutableStateOf(false) } - LaunchedEffect(showAdvertiseLAAlert) { - if ( - !chatModel.controller.appPrefs.laNoticeShown.get() - && showAdvertiseLAAlert - && chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete - && chatModel.chats.isNotEmpty() - && chatModel.activeCallInvitation.value == null - ) { - showLANotice() - } - } - LaunchedEffect(chatModel.showAdvertiseLAUnavailableAlert.value) { - if (chatModel.showAdvertiseLAUnavailableAlert.value) { - laUnavailableInstructionAlert() - } - } - LaunchedEffect(chatModel.clearOverlays.value) { - if (chatModel.clearOverlays.value) { - ModalManager.shared.closeModals() - chatModel.clearOverlays.value = false - } - } - - @Composable - fun AuthView() { - Surface(color = MaterialTheme.colors.background) { - Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - SimpleButton( - stringResource(MR.strings.auth_unlock), - icon = painterResource(MR.images.ic_lock), - click = { - laFailed.value = false - runAuthenticate() - } - ) - } - } - } - - Box { - val onboarding = chatModel.onboardingStage.value - val userCreated = chatModel.userCreated.value - var showInitializationView by remember { mutableStateOf(false) } - when { - chatModel.chatDbStatus.value == null && showInitializationView -> InitializationView() - showChatDatabaseError -> { - chatModel.chatDbStatus.value?.let { - DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs) - } - } - onboarding == null || userCreated == null -> SplashView() - onboarding == OnboardingStage.OnboardingComplete && userCreated -> { - Box { - showAdvertiseLAAlert = true - BoxWithConstraints { - var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) } - val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) } - Box( - Modifier - .graphicsLayer { - translationX = -offset.value.dp.toPx() - } - ) { - val stopped = chatModel.chatRunning.value == false - if (chatModel.sharedContent.value == null) - ChatListView(chatModel, setPerformLA, stopped) - else - ShareListView(chatModel, stopped) - } - val scope = rememberCoroutineScope() - val onComposed: () -> Unit = { - scope.launch { - offset.animateTo( - if (chatModel.chatId.value == null) 0f else maxWidth.value, - chatListAnimationSpec() - ) - if (offset.value == 0f) { - currentChatId = null - } - } - } - LaunchedEffect(Unit) { - launch { - snapshotFlow { chatModel.chatId.value } - .distinctUntilChanged() - .collect { - if (it != null) currentChatId = it - else onComposed() - } - } - } - Box (Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@ { - currentChatId?.let { - ChatView(it, chatModel, onComposed) - } - } - } - } - } - onboarding == OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) - onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {} - onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel) - onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) - } - ModalManager.shared.showInView() - val unauthorized = remember { derivedStateOf { userAuthorized.value != true } } - if (unauthorized.value && !(chatModel.activeCallViewIsVisible.value && chatModel.showCallView.value)) { - LaunchedEffect(Unit) { - // With these constrains when user presses back button while on ChatList, activity destroys and shows auth request - // while the screen moves to a launcher. Detect it and prevent showing the auth - if (!(destroyedAfterBackPress.value && chatModel.controller.appPrefs.laMode.get() == LAMode.SYSTEM)) { - runAuthenticate() - } - } - if (chatModel.controller.appPrefs.performLA.get() && laFailed.value) { - AuthView() - } else { - SplashView() - } - } else if (chatModel.showCallView.value) { - ActiveCallView(chatModel) - } - ModalManager.shared.showPasscodeInView() - val invitation = chatModel.activeCallInvitation.value - if (invitation != null) IncomingCallAlertView(invitation, chatModel) - AlertManager.shared.showInView() - - LaunchedEffect(Unit) { - delay(1000) - if (chatModel.chatDbStatus.value == null) { - showInitializationView = true - } - } - } - - DisposableEffectOnRotate { - // When using lock delay = 0 and screen rotates, the app will be locked which is not useful. - // Let's prolong the unlocked period to 3 sec for screen rotation to take place - if (chatModel.controller.appPrefs.laLockDelay.get() == 0) { - enteredBackground.value = elapsedRealtime() + 3000 - } - } -} - -@Composable -private fun InitializationView() { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CircularProgressIndicator( - Modifier - .padding(bottom = DEFAULT_PADDING) - .size(30.dp), - color = MaterialTheme.colors.secondary, - strokeWidth = 2.5.dp - ) - Text(stringResource(MR.strings.opening_database)) - } - } -} - fun processNotificationIntent(intent: Intent?, chatModel: ChatModel) { val userId = getUserIdFromIntent(intent) when (intent?.action) { @@ -667,35 +236,6 @@ fun processExternalIntent(intent: Intent?, chatModel: ChatModel) { fun isMediaIntent(intent: Intent): Boolean = intent.type?.startsWith("image/") == true || intent.type?.startsWith("video/") == true -fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { - Log.d(TAG, "connectIfOpenedViaUri: opened via link") - if (chatModel.currentUser.value == null) { - chatModel.appOpenUrl.value = uri - } else { - withUriAction(uri) { linkType -> - val title = when (linkType) { - ConnectionLinkType.CONTACT -> generalGetString(MR.strings.connect_via_contact_link) - ConnectionLinkType.INVITATION -> generalGetString(MR.strings.connect_via_invitation_link) - ConnectionLinkType.GROUP -> generalGetString(MR.strings.connect_via_group_link) - } - AlertManager.shared.showAlertDialog( - title = title, - text = if (linkType == ConnectionLinkType.GROUP) - generalGetString(MR.strings.you_will_join_group) - else - generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link), - confirmText = generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { - withApi { - Log.d(TAG, "connectIfOpenedViaUri: connecting") - connectViaUri(chatModel, linkType, uri) - } - } - ) - } - } -} - suspend fun awaitChatStartedIfNeeded(chatModel: ChatModel, timeout: Long = 30_000) { // Still decrypting database if (chatModel.chatRunning.value == null) { 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 64994115e2..15720dcd09 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 @@ -6,10 +6,10 @@ import android.util.Log import androidx.lifecycle.* import androidx.work.* import chat.simplex.app.model.* +import chat.simplex.app.platform.* import chat.simplex.app.ui.theme.DefaultTheme import chat.simplex.app.views.helpers.* import chat.simplex.app.views.onboarding.OnboardingStage -import chat.simplex.app.views.usersettings.NotificationsMode import com.jakewharton.processphoenix.ProcessPhoenix import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString @@ -22,76 +22,9 @@ import kotlin.concurrent.thread const val TAG = "SIMPLEX" -// ghc's rts -external fun initHS() -// android-support -external fun pipeStdOutToSocket(socketName: String) : Int - -// SimpleX API -typealias ChatCtrl = Long -external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array -external fun chatSendCmd(ctrl: ChatCtrl, msg: String): String -external fun chatRecvMsg(ctrl: ChatCtrl): String -external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String -external fun chatParseMarkdown(str: String): String -external fun chatParseServer(str: String): String -external fun chatPasswordHash(pwd: String, salt: String): String - class SimplexApp: Application(), LifecycleEventObserver { - var mainActivity: WeakReference = WeakReference(null) val chatModel: ChatModel get() = chatController.chatModel - val appPreferences: AppPreferences - get() = chatController.appPrefs - - val chatController: ChatController = ChatController - var isAppOnForeground: Boolean = false - - val defaultLocale: Locale = Locale.getDefault() - - suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) { - val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() - val dbAbsolutePathPrefix = getFilesDirectory() - val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp - val migrated: Array = chatMigrateInit(dbAbsolutePathPrefix, dbKey, confirm.value) - val res: DBMigrationResult = kotlin.runCatching { - json.decodeFromString(migrated[0] as String) - }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } - val ctrl = if (res is DBMigrationResult.OK) { - migrated[1] as Long - } else null - chatController.ctrl = ctrl - chatModel.chatDbEncrypted.value = dbKey != "" - chatModel.chatDbStatus.value = res - if (res != DBMigrationResult.OK) { - Log.d(TAG, "Unable to migrate successfully: $res") - } else if (startChat) { - // If we migrated successfully means previous re-encryption process on database level finished successfully too - if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) - val user = chatController.apiGetActiveUser() - if (user == null) { - chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo - chatModel.currentUser.value = null - chatModel.users.clear() - } else { - val savedOnboardingStage = appPreferences.onboardingStage.get() - chatModel.onboardingStage.value = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { - OnboardingStage.Step3_CreateSimpleXAddress - } else { - savedOnboardingStage - } - chatController.startChat(user) - // Prevents from showing "Enable notifications" alert when onboarding wasn't complete yet - if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) { - SimplexService.showBackgroundServiceNoticeIfNeeded() - if (appPreferences.notificationsMode.get() == NotificationsMode.SERVICE.name) - SimplexService.start() - } - } - } - } - override fun onCreate() { super.onCreate() @@ -99,6 +32,7 @@ class SimplexApp: Application(), LifecycleEventObserver { return; } context = this + initHaskell() context.getDir("temp", MODE_PRIVATE).deleteRecursively() withBGApi { initChatController() @@ -194,75 +128,7 @@ class SimplexApp: Application(), LifecycleEventObserver { MessagesFetcherWorker.scheduleWork() } - private fun runMigrations() { - val lastMigration = chatModel.controller.appPrefs.lastMigratedVersionCode - if (lastMigration.get() < BuildConfig.VERSION_CODE) { - while (true) { - if (lastMigration.get() < 117) { - if (chatModel.controller.appPrefs.currentTheme.get() == DefaultTheme.DARK.name) { - chatModel.controller.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name) - } - lastMigration.set(117) - } else { - lastMigration.set(BuildConfig.VERSION_CODE) - break - } - } - } - } - companion object { lateinit var context: SimplexApp private set - - init { - val socketName = BuildConfig.APPLICATION_ID + ".local.socket.address.listen.native.cmd2" - val s = Semaphore(0) - thread(name="stdout/stderr pipe") { - Log.d(TAG, "starting server") - var server: LocalServerSocket? = null - for (i in 0..100) { - try { - server = LocalServerSocket(socketName + i) - break - } catch (e: IOException) { - Log.e(TAG, e.stackTraceToString()) - } - } - if (server == null) { - throw Error("Unable to setup local server socket. Contact developers") - } - Log.d(TAG, "started server") - s.release() - val receiver = server.accept() - Log.d(TAG, "started receiver") - val logbuffer = FifoQueue(500) - if (receiver != null) { - val inStream = receiver.inputStream - val inStreamReader = InputStreamReader(inStream) - val input = BufferedReader(inStreamReader) - Log.d(TAG, "starting receiver loop") - while (true) { - val line = input.readLine() ?: break - Log.w("$TAG (stdout/stderr)", line) - logbuffer.add(line) - } - Log.w(TAG, "exited receiver loop") - } - } - - System.loadLibrary("app-lib") - - s.acquire() - pipeStdOutToSocket(socketName) - - initHS() - } - } -} - -class FifoQueue(private var capacity: Int) : LinkedList() { - override fun add(element: E): Boolean { - if(size > capacity) removeFirst() - return super.add(element) } } 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 202471919f..135ab0b664 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 @@ -16,10 +16,8 @@ import androidx.compose.ui.unit.dp import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.work.* -import chat.simplex.app.model.ChatController -import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.* import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.NotificationsMode import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -97,7 +95,7 @@ class SimplexService: Service() { val self = this isStartingService = true withApi { - val chatController = (application as SimplexApp).chatController + val chatController = ChatController waitDbMigrationEnds(chatController) try { Log.w(TAG, "Starting foreground service") @@ -167,7 +165,7 @@ class SimplexService: Service() { // re-schedules the task when "Clear recent apps" is pressed override fun onTaskRemoved(rootIntent: Intent) { // Just to make sure that after restart of the app the user will need to re-authenticate - MainActivity.clearAuthState() + AppLock.clearAuthState() // If notification service isn't enabled or battery optimization isn't disabled, we shouldn't restart the service if (!SimplexApp.context.allowToStartServiceAfterAppExit()) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/helpers/Extensions.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/helpers/Extensions.kt new file mode 100644 index 0000000000..61a1f2e836 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/helpers/Extensions.kt @@ -0,0 +1,39 @@ +package chat.simplex.app.helpers + +import android.app.Activity +import android.content.res.Configuration +import chat.simplex.app.SimplexApp +import chat.simplex.app.model.SharedPreference +import chat.simplex.app.platform.defaultLocale +import java.util.* + +fun saveAppLocale(pref: SharedPreference, activity: Activity, languageCode: String? = null) { + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // val localeManager = SimplexApp.context.getSystemService(LocaleManager::class.java) + // localeManager.applicationLocales = LocaleList(Locale.forLanguageTag(languageCode ?: return)) + // } else { + pref.set(languageCode) + if (languageCode == null) { + activity.applyLocale(defaultLocale) + } + activity.recreate() + // } +} + +fun Activity.applyAppLocale(pref: SharedPreference) { + // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + val lang = pref.get() + if (lang == null || lang == Locale.getDefault().language) return + applyLocale(Locale.forLanguageTag(lang)) + // } +} + +private fun Activity.applyLocale(locale: Locale) { + Locale.setDefault(locale) + val appConf = Configuration(SimplexApp.context.resources.configuration).apply { setLocale(locale) } + val activityConf = Configuration(resources.configuration).apply { setLocale(locale) } + @Suppress("DEPRECATION") + SimplexApp.context.resources.updateConfiguration(appConf, resources.displayMetrics) + @Suppress("DEPRECATION") + resources.updateConfiguration(activityConf, resources.displayMetrics) +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/ChatModel.kt index f7ff5a80c8..2ad06648c5 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -14,7 +14,6 @@ import chat.simplex.app.views.chat.ComposeState import chat.simplex.app.views.helpers.* import chat.simplex.app.views.onboarding.OnboardingStage import chat.simplex.app.views.usersettings.NotificationPreviewMode -import chat.simplex.app.views.usersettings.NotificationsMode import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt index f3b7e7a180..031a544260 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt @@ -13,6 +13,8 @@ import android.util.Log import android.view.Display import androidx.core.app.* import chat.simplex.app.* +import chat.simplex.app.platform.base64ToBitmap +import chat.simplex.app.platform.isAppOnForeground import chat.simplex.app.views.call.* import chat.simplex.app.views.chatlist.acceptContactRequest import chat.simplex.app.views.helpers.* @@ -172,9 +174,9 @@ object NtfManager { "notifyCallInvitation pre-requests: " + "keyguard locked ${keyguardManager.isKeyguardLocked}, " + "callOnLockScreen ${appPreferences.callOnLockScreen.get()}, " + - "onForeground ${SimplexApp.context.isAppOnForeground}" + "onForeground ${isAppOnForeground}" ) - if (SimplexApp.context.isAppOnForeground) return + if (isAppOnForeground) return val contactId = invitation.contact.id Log.d(TAG, "notifyCallInvitation $contactId") val image = invitation.contact.image diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index f7d7de42b8..737344be1f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -1,6 +1,7 @@ package chat.simplex.app.model import android.content.* +import android.os.Build import android.util.Log import chat.simplex.app.views.helpers.* import androidx.compose.runtime.* @@ -8,6 +9,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import dev.icerock.moko.resources.compose.painterResource import chat.simplex.app.* +import chat.simplex.app.platform.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.call.* import chat.simplex.app.views.newchat.ConnectViaLinkTab @@ -1379,7 +1381,7 @@ object ChatController { || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) { withApi { receiveFile(r.user, file.fileId) } } - if (cItem.showNotification && (!SimplexApp.context.isAppOnForeground || chatModel.chatId.value != cInfo.id)) { + if (cItem.showNotification && (!isAppOnForeground || chatModel.chatId.value != cInfo.id)) { ntfManager.notifyMessageReceived(r.user, cInfo, cItem) } } @@ -3833,3 +3835,14 @@ sealed class ArchiveError { @Serializable @SerialName("import") class ArchiveErrorImport(val chatError: ChatError): ArchiveError() @Serializable @SerialName("importFile") class ArchiveErrorImportFile(val file: String, val chatError: ChatError): ArchiveError() } + +enum class NotificationsMode(private val requiresIgnoringBatterySinceSdk: Int) { + OFF(Int.MAX_VALUE), PERIODIC(Build.VERSION_CODES.M), SERVICE(Build.VERSION_CODES.S), /*INSTANT(Int.MAX_VALUE) - for Firebase notifications */; + + val requiresIgnoringBattery + get() = requiresIgnoringBatterySinceSdk <= Build.VERSION.SDK_INT + + companion object { + val default: NotificationsMode = SERVICE + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt new file mode 100644 index 0000000000..4d89009642 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt @@ -0,0 +1,30 @@ +package chat.simplex.app.platform + +import chat.simplex.app.BuildConfig +import chat.simplex.app.model.ChatController +import chat.simplex.app.ui.theme.DefaultTheme +import java.util.* + +class FifoQueue(private var capacity: Int) : LinkedList() { + override fun add(element: E): Boolean { + if(size > capacity) removeFirst() + return super.add(element) + } +} + +fun runMigrations() { + val lastMigration = ChatController.appPrefs.lastMigratedVersionCode + if (lastMigration.get() < BuildConfig.VERSION_CODE) { + while (true) { + if (lastMigration.get() < 117) { + if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.DARK.name) { + ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name) + } + lastMigration.set(117) + } else { + lastMigration.set(BuildConfig.VERSION_CODE) + break + } + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.kt new file mode 100644 index 0000000000..4fc20c504b --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.kt @@ -0,0 +1,61 @@ +package chat.simplex.app.platform + +import android.net.LocalServerSocket +import android.util.Log +import chat.simplex.app.* +import java.io.* +import java.lang.ref.WeakReference +import java.util.* +import java.util.concurrent.Semaphore +import kotlin.concurrent.thread + +var isAppOnForeground: Boolean = false + +@Suppress("ConstantLocale") +val defaultLocale: Locale = Locale.getDefault() + +var mainActivity: WeakReference = WeakReference(null) + +fun initHaskell() { + val socketName = BuildConfig.APPLICATION_ID + ".local.socket.address.listen.native.cmd2" + val s = Semaphore(0) + thread(name="stdout/stderr pipe") { + Log.d(TAG, "starting server") + var server: LocalServerSocket? = null + for (i in 0..100) { + try { + server = LocalServerSocket(socketName + i) + break + } catch (e: IOException) { + Log.e(TAG, e.stackTraceToString()) + } + } + if (server == null) { + throw Error("Unable to setup local server socket. Contact developers") + } + Log.d(TAG, "started server") + s.release() + val receiver = server.accept() + Log.d(TAG, "started receiver") + val logbuffer = FifoQueue(500) + if (receiver != null) { + val inStream = receiver.inputStream + val inStreamReader = InputStreamReader(inStream) + val input = BufferedReader(inStreamReader) + Log.d(TAG, "starting receiver loop") + while (true) { + val line = input.readLine() ?: break + Log.w("$TAG (stdout/stderr)", line) + logbuffer.add(line) + } + Log.w(TAG, "exited receiver loop") + } + } + + System.loadLibrary("app-lib") + + s.acquire() + pipeStdOutToSocket(socketName) + + initHS() +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Backend.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Backend.common.kt new file mode 100644 index 0000000000..c4e6d043c8 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Backend.common.kt @@ -0,0 +1,76 @@ +package chat.simplex.app.platform + +import android.util.Log +import chat.simplex.app.SimplexService +import chat.simplex.app.TAG +import chat.simplex.app.model.* +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.onboarding.OnboardingStage +import kotlinx.serialization.decodeFromString + +// ghc's rts +external fun initHS() +// android-support +external fun pipeStdOutToSocket(socketName: String) : Int + +// SimpleX API +typealias ChatCtrl = Long +external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array +external fun chatSendCmd(ctrl: ChatCtrl, msg: String): String +external fun chatRecvMsg(ctrl: ChatCtrl): String +external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String +external fun chatParseMarkdown(str: String): String +external fun chatParseServer(str: String): String +external fun chatPasswordHash(pwd: String, salt: String): String + +val chatModel: ChatModel + get() = chatController.chatModel + +val appPreferences: AppPreferences + get() = chatController.appPrefs + +val chatController: ChatController = ChatController + + +suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) { + val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() + val dbAbsolutePathPrefix = getFilesDirectory() + val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp + val migrated: Array = chatMigrateInit(dbAbsolutePathPrefix, dbKey, confirm.value) + val res: DBMigrationResult = kotlin.runCatching { + json.decodeFromString(migrated[0] as String) + }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } + val ctrl = if (res is DBMigrationResult.OK) { + migrated[1] as Long + } else null + chatController.ctrl = ctrl + chatModel.chatDbEncrypted.value = dbKey != "" + chatModel.chatDbStatus.value = res + if (res != DBMigrationResult.OK) { + Log.d(TAG, "Unable to migrate successfully: $res") + } else if (startChat) { + // If we migrated successfully means previous re-encryption process on database level finished successfully too + if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) + val user = chatController.apiGetActiveUser() + if (user == null) { + chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo + chatModel.currentUser.value = null + chatModel.users.clear() + } else { + val savedOnboardingStage = appPreferences.onboardingStage.get() + chatModel.onboardingStage.value = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { + OnboardingStage.Step3_CreateSimpleXAddress + } else { + savedOnboardingStage + } + chatController.startChat(user) + // Prevents from showing "Enable notifications" alert when onboarding wasn't complete yet + if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) { + SimplexService.showBackgroundServiceNoticeIfNeeded() + if (appPreferences.notificationsMode.get() == NotificationsMode.SERVICE.name) + SimplexService.start() + } + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt new file mode 100644 index 0000000000..7d3c592ab4 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt @@ -0,0 +1,30 @@ +package chat.simplex.app.platform + +import chat.simplex.app.SimplexApp +import chat.simplex.app.model.CIFile +import java.io.File + +fun getFilesDirectory(): String { + return SimplexApp.context.filesDir.toString() +} + +fun getTempFilesDirectory(): String { + return "${getFilesDirectory()}/temp_files" +} + +fun getAppFilesDirectory(): String { + return "${getFilesDirectory()}/app_files" +} + +fun getAppFilePath(fileName: String): String { + return "${getAppFilesDirectory()}/$fileName" +} + +fun getLoadedFilePath(file: CIFile?): String? { + return if (file?.filePath != null && file.loaded) { + val filePath = getAppFilePath(file.filePath) + if (File(filePath).exists()) filePath else null + } else { + null + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Images.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Images.kt new file mode 100644 index 0000000000..7566046043 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Images.kt @@ -0,0 +1,106 @@ +package chat.simplex.app.platform + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.drawable.AnimatedImageDrawable +import android.net.Uri +import android.os.Build +import android.util.Base64 +import android.util.Log +import android.webkit.MimeTypeMap +import androidx.core.graphics.applyCanvas +import androidx.core.graphics.drawable.toBitmap +import chat.simplex.app.* +import chat.simplex.app.views.helpers.errorBitmap +import chat.simplex.app.views.helpers.getFileName +import java.io.ByteArrayOutputStream +import kotlin.math.min +import kotlin.math.sqrt + +fun base64ToBitmap(base64ImageString: String): Bitmap { + val imageString = base64ImageString + .removePrefix("data:image/png;base64,") + .removePrefix("data:image/jpg;base64,") + try { + val imageBytes = Base64.decode(imageString, Base64.NO_WRAP) + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + } catch (e: Exception) { + Log.e(TAG, "base64ToBitmap error: $e") + return errorBitmap + } +} + +fun resizeImageToStrSize(image: Bitmap, maxDataSize: Long): String { + var img = image + var str = compressImageStr(img) + while (str.length > maxDataSize) { + val ratio = sqrt(str.length.toDouble() / maxDataSize.toDouble()) + val clippedRatio = min(ratio, 2.0) + val width = (img.width.toDouble() / clippedRatio).toInt() + val height = img.height * width / img.width + img = Bitmap.createScaledBitmap(img, width, height, true) + str = compressImageStr(img) + } + return str +} + +// Inspired by https://github.com/MakeItEasyDev/Jetpack-Compose-Capture-Image-Or-Choose-from-Gallery +fun cropToSquare(image: Bitmap): Bitmap { + var xOffset = 0 + var yOffset = 0 + val side = min(image.height, image.width) + if (image.height < image.width) { + xOffset = (image.width - side) / 2 + } else { + yOffset = (image.height - side) / 2 + } + return Bitmap.createBitmap(image, xOffset, yOffset, side, side) +} + +private fun compressImageStr(bitmap: Bitmap): String { + val usePng = bitmap.hasAlpha() + val ext = if (usePng) "png" else "jpg" + return "data:image/$ext;base64," + Base64.encodeToString(compressImageData(bitmap, usePng).toByteArray(), Base64.NO_WRAP) +} + +private fun compressImageData(bitmap: Bitmap, usePng: Boolean): ByteArrayOutputStream { + val stream = ByteArrayOutputStream() + bitmap.compress(if (!usePng) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG, 85, stream) + return stream +} + +fun resizeImageToDataSize(image: Bitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream { + var img = image + var stream = compressImageData(img, usePng) + while (stream.size() > maxDataSize) { + val ratio = sqrt(stream.size().toDouble() / maxDataSize.toDouble()) + val clippedRatio = min(ratio, 2.0) + val width = (img.width.toDouble() / clippedRatio).toInt() + val height = img.height * width / img.width + img = Bitmap.createScaledBitmap(img, width, height, true) + stream = compressImageData(img, usePng) + } + return stream +} + +fun Bitmap.addLogo(): Bitmap = applyCanvas { + val radius = (width * 0.16f) / 2 + val paint = android.graphics.Paint() + paint.color = android.graphics.Color.WHITE + drawCircle(width / 2f, height / 2f, radius, paint) + val logo = SimplexApp.context.resources.getDrawable(R.mipmap.icon_foreground, null).toBitmap() + val logoSize = (width * 0.24).toInt() + translate((width - logoSize) / 2f, (height - logoSize) / 2f) + drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null) +} + +fun isImage(uri: Uri): Boolean = + MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileName(uri)?.split(".")?.last())?.contains("image/") == true + + +fun isAnimImage(uri: Uri, drawable: Any?): Boolean { + val isAnimNewApi = Build.VERSION.SDK_INT >= 28 && drawable is AnimatedImageDrawable + val isAnimOldApi = Build.VERSION.SDK_INT < 28 && + (getFileName(uri)?.endsWith(".gif") == true || getFileName(uri)?.endsWith(".webp") == true) + return isAnimNewApi || isAnimOldApi +} \ No newline at end of file diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt new file mode 100644 index 0000000000..11305db50a --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt @@ -0,0 +1,158 @@ +package chat.simplex.app.platform + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.text.InputType +import android.util.Log +import android.view.OnReceiveContentListener +import android.view.ViewGroup +import android.view.inputmethod.* +import android.widget.EditText +import android.widget.TextView +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.inputmethod.EditorInfoCompat +import androidx.core.view.inputmethod.InputConnectionCompat +import androidx.core.widget.doAfterTextChanged +import androidx.core.widget.doOnTextChanged +import chat.simplex.app.* +import chat.simplex.app.R +import chat.simplex.app.model.ChatModel +import chat.simplex.app.ui.theme.CurrentColors +import chat.simplex.app.views.chat.* +import chat.simplex.app.views.helpers.SharedContent +import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource +import kotlinx.coroutines.delay +import java.lang.reflect.Field + +@Composable +fun NativeKeyboard( + composeState: MutableState, + textStyle: MutableState, + showDeleteTextButton: MutableState, + userIsObserver: Boolean, + onMessageChange: (String) -> Unit +) { + val cs = composeState.value + val textColor = MaterialTheme.colors.onBackground + val tintColor = MaterialTheme.colors.secondaryVariant + val padding = PaddingValues(12.dp, 7.dp, 45.dp, 0.dp) + val paddingStart = with(LocalDensity.current) { 12.dp.roundToPx() } + val paddingTop = with(LocalDensity.current) { 7.dp.roundToPx() } + val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() } + val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() } + var showKeyboard by remember { mutableStateOf(false) } + LaunchedEffect(cs.contextItem) { + if (cs.contextItem is ComposeContextItem.QuotedItem) { + delay(100) + showKeyboard = true + } else if (cs.contextItem is ComposeContextItem.EditingItem) { + // Keyboard will not show up if we try to show it too fast + delay(300) + showKeyboard = true + } + } + + AndroidView(modifier = Modifier, factory = { + val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) { + override fun setOnReceiveContentListener( + mimeTypes: Array?, + listener: OnReceiveContentListener? + ) { + super.setOnReceiveContentListener(mimeTypes, listener) + } + + override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { + val connection = super.onCreateInputConnection(editorInfo) + EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) + val onCommit = InputConnectionCompat.OnCommitContentListener { inputContentInfo, _, _ -> + try { + inputContentInfo.requestPermission() + } catch (e: Exception) { + return@OnCommitContentListener false + } + ChatModel.sharedContent.value = SharedContent.Media("", listOf(inputContentInfo.contentUri)) + true + } + return InputConnectionCompat.createWrapper(connection, editorInfo, onCommit) + } + } + editText.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + editText.maxLines = 16 + editText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or editText.inputType + editText.setTextColor(textColor.toArgb()) + editText.textSize = textStyle.value.fontSize.value + val drawable = it.getDrawable(R.drawable.send_msg_view_background)!! + DrawableCompat.setTint(drawable, tintColor.toArgb()) + editText.background = drawable + editText.setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom) + editText.setText(cs.message) + if (Build.VERSION.SDK_INT >= 29) { + editText.textCursorDrawable?.let { DrawableCompat.setTint(it, CurrentColors.value.colors.secondary.toArgb()) } + } else { + try { + val f: Field = TextView::class.java.getDeclaredField("mCursorDrawableRes") + f.isAccessible = true + f.set(editText, R.drawable.edit_text_cursor) + } catch (e: Exception) { + Log.e(TAG, e.stackTraceToString()) + } + } + editText.doOnTextChanged { text, _, _, _ -> + if (!composeState.value.inProgress) { + onMessageChange(text.toString()) + } else if (text.toString() != composeState.value.message) { + editText.setText(composeState.value.message) + } + } + editText.doAfterTextChanged { text -> if (composeState.value.preview is ComposePreview.VoicePreview && text.toString() != "") editText.setText("") } + editText + }) { + it.setTextColor(textColor.toArgb()) + it.textSize = textStyle.value.fontSize.value + DrawableCompat.setTint(it.background, tintColor.toArgb()) + it.isFocusable = composeState.value.preview !is ComposePreview.VoicePreview + it.isFocusableInTouchMode = it.isFocusable + if (cs.message != it.text.toString()) { + it.setText(cs.message) + // Set cursor to the end of the text + it.setSelection(it.text.length) + } + if (showKeyboard) { + it.requestFocus() + val imm: InputMethodManager = SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT) + showKeyboard = false + } + showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress + } + if (composeState.value.preview is ComposePreview.VoicePreview) { + ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding) + } else if (userIsObserver) { + ComposeOverlay(MR.strings.you_are_observer, textStyle, padding) + } +} + +@Composable +private fun ComposeOverlay(textId: StringResource, textStyle: MutableState, padding: PaddingValues) { + Text( + generalGetString(textId), + Modifier.padding(padding), + color = MaterialTheme.colors.secondary, + style = textStyle.value.copy(fontStyle = FontStyle.Italic) + ) +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt new file mode 100644 index 0000000000..c0065b3e23 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt @@ -0,0 +1,9 @@ +package chat.simplex.app.platform + +import android.app.UiModeManager +import android.content.Context +import chat.simplex.app.SimplexApp + +// Non-@Composable implementation +fun isInNightMode() = + (SimplexApp.context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager).nightMode == UiModeManager.MODE_NIGHT_YES diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Share.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Share.kt similarity index 76% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Share.kt rename to apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Share.kt index 10ef455a48..36559d71f4 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Share.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Share.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.app.platform import android.Manifest import android.content.* @@ -7,14 +7,11 @@ import android.provider.MediaStore import android.util.Log import android.webkit.MimeTypeMap import android.widget.Toast -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.runtime.Composable import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import chat.simplex.app.* import chat.simplex.app.model.CIFile +import chat.simplex.app.views.helpers.generalGetString import chat.simplex.res.MR import java.io.BufferedOutputStream import java.io.File @@ -67,29 +64,6 @@ fun sendEmail(subject: String, body: CharSequence) { } } -@Composable -fun rememberSaveFileLauncher(ciFile: CIFile?): ManagedActivityResultLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument(), - onResult = { destination -> - destination?.let { - val cxt = SimplexApp.context - val filePath = getLoadedFilePath(ciFile) - if (filePath != null) { - val contentResolver = cxt.contentResolver - contentResolver.openOutputStream(destination)?.let { stream -> - val outputStream = BufferedOutputStream(stream) - File(filePath).inputStream().use { it.copyTo(outputStream) } - outputStream.close() - Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(cxt, generalGetString(MR.strings.file_not_found), Toast.LENGTH_SHORT).show() - } - } - } - ) - fun imageMimeType(fileName: String): String { val lowercaseName = fileName.lowercase() return when { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/UI.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/UI.kt new file mode 100644 index 0000000000..4197cffb04 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/UI.kt @@ -0,0 +1,61 @@ +package chat.simplex.app.platform + +import android.app.Activity +import android.content.Context +import android.content.pm.ActivityInfo +import android.graphics.Rect +import android.os.Build +import android.view.* +import android.view.inputmethod.InputMethodManager +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import chat.simplex.app.SimplexApp +import chat.simplex.app.views.helpers.KeyboardState + +@Composable +fun LockToCurrentOrientationUntilDispose() { + val context = LocalContext.current + DisposableEffect(Unit) { + val activity = context as Activity + val manager = context.getSystemService(Activity.WINDOW_SERVICE) as WindowManager + val rotation = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) manager.defaultDisplay.rotation else activity.display?.rotation + activity.requestedOrientation = when (rotation) { + Surface.ROTATION_90 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + Surface.ROTATION_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT + Surface.ROTATION_270 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + else -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + // Unlock orientation + onDispose { activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } + } +} + +@Composable +fun getKeyboardState(): State { + val keyboardState = remember { mutableStateOf(KeyboardState.Closed) } + val view = LocalView.current + DisposableEffect(view) { + val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener { + val rect = Rect() + view.getWindowVisibleDisplayFrame(rect) + val screenHeight = view.rootView.height + val keypadHeight = screenHeight - rect.bottom + keyboardState.value = if (keypadHeight > screenHeight * 0.15) { + KeyboardState.Opened + } else { + KeyboardState.Closed + } + } + view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener) + + onDispose { + view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) + } + } + + return keyboardState +} + +fun hideKeyboard(view: View) = + (SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Theme.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Theme.kt index 0258ccbea0..75aa950d21 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Theme.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Theme.kt @@ -1,7 +1,5 @@ package chat.simplex.app.ui.theme -import android.app.UiModeManager -import android.content.Context import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.* @@ -10,8 +8,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.* import androidx.compose.ui.unit.dp -import chat.simplex.app.R import chat.simplex.app.SimplexApp +import chat.simplex.app.platform.isInNightMode import chat.simplex.app.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.flow.MutableStateFlow @@ -254,10 +252,6 @@ val SimplexColorPaletteApp = AppColors( val CurrentColors: MutableStateFlow = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) -// Non-@Composable implementation -private fun isInNightMode() = - (SimplexApp.context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager).nightMode == UiModeManager.MODE_NIGHT_YES - @Composable fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt new file mode 100644 index 0000000000..e20c7dbbde --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt @@ -0,0 +1,50 @@ +package chat.simplex.app.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + h1 = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Bold, + fontSize = 32.sp, + ), + h2 = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 24.sp + ), + h3 = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 18.5.sp + ), + h4 = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 17.5.sp + ), + body1 = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ), + body2 = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 14.sp + ), + button = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + ), + caption = TextStyle( + fontFamily = Inter, + fontWeight = FontWeight.Normal, + fontSize = 18.sp + ) +) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.kt index a67a14d0c1..299156ecd0 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.kt @@ -1,9 +1,6 @@ package chat.simplex.app.ui.theme -import androidx.compose.material.Typography -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.* -import androidx.compose.ui.unit.sp import chat.simplex.res.MR // https://github.com/rsms/inter @@ -15,47 +12,3 @@ val Inter: FontFamily = FontFamily( Font(MR.fonts.Inter.medium.fontResourceId, FontWeight.Medium), Font(MR.fonts.Inter.light.fontResourceId, FontWeight.Light) ) - -// Set of Material typography styles to start with -val Typography = Typography( - h1 = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Bold, - fontSize = 32.sp, - ), - h2 = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 24.sp - ), - h3 = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 18.5.sp - ), - h4 = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 17.5.sp - ), - body1 = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 16.sp - ), - body2 = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 14.sp - ), - button = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - ), - caption = TextStyle( - fontFamily = Inter, - fontWeight = FontWeight.Normal, - fontSize = 18.sp - ) -) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/TerminalView.kt index 264c243ebd..5b305d6a13 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.model.* +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.* import chat.simplex.app.views.helpers.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallManager.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallManager.kt index b87f996f8e..ae59623482 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallManager.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallManager.kt @@ -3,7 +3,6 @@ package chat.simplex.app.views.call import android.util.Log import chat.simplex.app.TAG import chat.simplex.app.model.ChatModel -import chat.simplex.app.views.helpers.ModalManager import chat.simplex.app.views.helpers.withApi import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallView.kt index f937342f55..2310479ce7 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallView.kt @@ -34,12 +34,10 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewClientCompat import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.ProfileImage import chat.simplex.app.views.helpers.withApi -import chat.simplex.app.views.usersettings.NotificationsMode import com.google.accompanist.permissions.rememberMultiplePermissionsState import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/WebRTC.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/WebRTC.kt index cec29c94fe..fbd35d71ad 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/WebRTC.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/WebRTC.kt @@ -2,9 +2,7 @@ package chat.simplex.app.views.call import androidx.compose.runtime.Composable import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.* -import chat.simplex.app.model.Contact -import chat.simplex.app.model.User +import chat.simplex.app.model.* import chat.simplex.app.views.helpers.generalGetString import chat.simplex.res.MR import kotlinx.datetime.Instant @@ -214,7 +212,7 @@ fun parseRTCIceServers(servers: List): List? { } fun getIceServers(): List? { - val value = SimplexApp.context.chatController.appPrefs.webrtcIceServers.get() ?: return null + val value = ChatController.appPrefs.webrtcIceServers.get() ?: return null val servers: List = value.split("\n") return parseRTCIceServers(servers) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index f7e28ff365..dca2a04e52 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -29,9 +29,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R import chat.simplex.app.SimplexApp import chat.simplex.app.model.* +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.QRCode diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatItemInfoView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatItemInfoView.kt index 6eebb7901d..c0a63b6dd0 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatItemInfoView.kt @@ -18,8 +18,9 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.copyText +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.CurrentColors import chat.simplex.app.ui.theme.DEFAULT_PADDING import chat.simplex.app.views.chat.item.ItemAction diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 6f7b61725c..1e8e31360f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -30,8 +30,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.* import androidx.core.content.FileProvider import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.call.* import chat.simplex.app.views.chat.group.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt index 3c36a6e2af..6f8e89722a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt @@ -15,9 +15,9 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.app.R +import chat.simplex.app.platform.base64ToBitmap import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.UploadContent -import chat.simplex.app.views.helpers.base64ToBitmap import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt new file mode 100644 index 0000000000..f5c47fc13b --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt @@ -0,0 +1,759 @@ +@file:UseSerializers(UriSerializer::class) +package chat.simplex.app.views.chat + +import ComposeFileView +import ComposeVoiceView +import android.app.Activity +import android.graphics.* +import android.net.Uri +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.dp +import chat.simplex.app.model.* +import chat.simplex.app.platform.* +import chat.simplex.app.views.chat.item.* +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.serialization.* +import java.io.File +import java.nio.file.Files + +@Serializable +sealed class ComposePreview { + @Serializable object NoPreview: ComposePreview() + @Serializable class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview() + @Serializable class MediaPreview(val images: List, val content: List): ComposePreview() + @Serializable data class VoicePreview(val voice: String, val durationMs: Int, val finished: Boolean): ComposePreview() + @Serializable class FilePreview(val fileName: String, val uri: Uri): ComposePreview() +} + +@Serializable +sealed class ComposeContextItem { + @Serializable object NoContextItem: ComposeContextItem() + @Serializable class QuotedItem(val chatItem: ChatItem): ComposeContextItem() + @Serializable class EditingItem(val chatItem: ChatItem): ComposeContextItem() +} + +@Serializable +data class LiveMessage( + val chatItem: ChatItem, + val typedMsg: String, + val sentMsg: String, + val sent: Boolean +) + +@Serializable +data class ComposeState( + val message: String = "", + val liveMessage: LiveMessage? = null, + val preview: ComposePreview = ComposePreview.NoPreview, + val contextItem: ComposeContextItem = ComposeContextItem.NoContextItem, + val inProgress: Boolean = false, + val useLinkPreviews: Boolean +) { + constructor(editingItem: ChatItem, liveMessage: LiveMessage? = null, useLinkPreviews: Boolean): this( + editingItem.content.text, + liveMessage, + chatItemPreview(editingItem), + ComposeContextItem.EditingItem(editingItem), + useLinkPreviews = useLinkPreviews + ) + + val editing: Boolean + get() = + when (contextItem) { + is ComposeContextItem.EditingItem -> true + else -> false + } + val sendEnabled: () -> Boolean + get() = { + val hasContent = when (preview) { + is ComposePreview.MediaPreview -> true + is ComposePreview.VoicePreview -> true + is ComposePreview.FilePreview -> true + else -> message.isNotEmpty() || liveMessage != null + } + hasContent && !inProgress + } + val endLiveDisabled: Boolean + get() = liveMessage != null && message.isEmpty() && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem + + val linkPreviewAllowed: Boolean + get() = + when (preview) { + is ComposePreview.MediaPreview -> false + is ComposePreview.VoicePreview -> false + is ComposePreview.FilePreview -> false + else -> useLinkPreviews + } + val linkPreview: LinkPreview? + get() = + when (preview) { + is ComposePreview.CLinkPreview -> preview.linkPreview + else -> null + } + + val attachmentDisabled: Boolean + get() { + if (editing || liveMessage != null || inProgress) return true + return when (preview) { + ComposePreview.NoPreview -> false + is ComposePreview.CLinkPreview -> false + else -> true + } + } + + val empty: Boolean + get() = message.isEmpty() && preview is ComposePreview.NoPreview + + companion object { + fun saver(): Saver, *> = Saver( + save = { json.encodeToString(serializer(), it.value) }, + restore = { + mutableStateOf(json.decodeFromString(it)) + } + ) + } +} + +sealed class RecordingState { + object NotStarted: RecordingState() + class Started(val filePath: String, val progressMs: Int = 0): RecordingState() + class Finished(val filePath: String, val durationMs: Int): RecordingState() + + val filePathNullable: String? + get() = (this as? Started)?.filePath +} + +fun chatItemPreview(chatItem: ChatItem): ComposePreview { + val fileName = chatItem.file?.fileName ?: "" + return when (val mc = chatItem.content.msgContent) { + is MsgContent.MCText -> ComposePreview.NoPreview + is MsgContent.MCLink -> ComposePreview.CLinkPreview(linkPreview = mc.preview) + // TODO: include correct type + is MsgContent.MCImage -> ComposePreview.MediaPreview(images = listOf(mc.image), listOf(UploadContent.SimpleImage(getAppFileUri(fileName)))) + is MsgContent.MCVideo -> ComposePreview.MediaPreview(images = listOf(mc.image), listOf(UploadContent.SimpleImage(getAppFileUri(fileName)))) + is MsgContent.MCVoice -> ComposePreview.VoicePreview(voice = fileName, mc.duration / 1000, true) + is MsgContent.MCFile -> ComposePreview.FilePreview(fileName, getAppFileUri(fileName)) + is MsgContent.MCUnknown, null -> ComposePreview.NoPreview + } +} + +@Composable +fun ComposeView( + chatModel: ChatModel, + chat: Chat, + composeState: MutableState, + attachmentOption: MutableState, + showChooseAttachment: () -> Unit +) { + val context = LocalContext.current + val linkUrl = rememberSaveable { mutableStateOf(null) } + val prevLinkUrl = rememberSaveable { mutableStateOf(null) } + val pendingLinkUrl = rememberSaveable { mutableStateOf(null) } + val cancelledLinks = rememberSaveable { mutableSetOf() } + val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() + val maxFileSize = getMaxFileSize(FileProtocol.XFTP) + val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) + val textStyle = remember { mutableStateOf(smallFont) } + val processPickedMedia = { uris: List, text: String? -> + val content = ArrayList() + val imagesPreview = ArrayList() + uris.forEach { uri -> + var bitmap: Bitmap? = null + when { + isImage(uri) -> { + // Image + val drawable = getDrawableFromUri(uri) + bitmap = if (drawable != null) getBitmapFromUri(uri) else null + if (isAnimImage(uri, drawable)) { + // It's a gif or webp + val fileSize = getFileSize(uri) + if (fileSize != null && fileSize <= maxFileSize) { + content.add(UploadContent.AnimatedImage(uri)) + } else { + bitmap = null + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.large_file), + String.format(generalGetString(MR.strings.maximum_supported_file_size), formatBytes(maxFileSize)) + ) + } + } else { + content.add(UploadContent.SimpleImage(uri)) + } + } + else -> { + // Video + val res = getBitmapFromVideo(uri) + bitmap = res.preview + val durationMs = res.duration + content.add(UploadContent.Video(uri, durationMs?.div(1000)?.toInt() ?: 0)) + } + } + if (bitmap != null) { + imagesPreview.add(resizeImageToStrSize(bitmap, maxDataSize = 14000)) + } + } + if (imagesPreview.isNotEmpty()) { + composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.MediaPreview(imagesPreview, content)) + } + } + val processPickedFile = { uri: Uri?, text: String? -> + if (uri != null) { + val fileSize = getFileSize(uri) + if (fileSize != null && fileSize <= maxFileSize) { + val fileName = getFileName(uri) + if (fileName != null) { + composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.FilePreview(fileName, uri)) + } + } else { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.large_file), + String.format(generalGetString(MR.strings.maximum_supported_file_size), formatBytes(maxFileSize)) + ) + } + } + } + val recState: MutableState = remember { mutableStateOf(RecordingState.NotStarted) } + + AttachmentSelection(composeState, attachmentOption, processPickedFile, processPickedMedia) + + fun isSimplexLink(link: String): Boolean = + link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true) + + fun parseMessage(msg: String): String? { + val parsedMsg = runBlocking { chatModel.controller.apiParseMarkdown(msg) } + val link = parsedMsg?.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) } + return link?.text + } + + fun loadLinkPreview(url: String, wait: Long? = null) { + if (pendingLinkUrl.value == url) { + composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null)) + withApi { + if (wait != null) delay(wait) + val lp = getLinkPreview(url) + if (lp != null && pendingLinkUrl.value == url) { + composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(lp)) + pendingLinkUrl.value = null + } else if (pendingLinkUrl.value == url) { + composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) + pendingLinkUrl.value = null + } + } + } + } + + fun showLinkPreview(s: String) { + prevLinkUrl.value = linkUrl.value + linkUrl.value = parseMessage(s) + val url = linkUrl.value + if (url != null) { + if (url != composeState.value.linkPreview?.uri && url != pendingLinkUrl.value) { + pendingLinkUrl.value = url + loadLinkPreview(url, wait = if (prevLinkUrl.value == url) null else 1500L) + } + } else { + composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) + } + } + + fun resetLinkPreview() { + linkUrl.value = null + prevLinkUrl.value = null + pendingLinkUrl.value = null + cancelledLinks.clear() + } + + fun clearState(live: Boolean = false) { + if (live) { + composeState.value = composeState.value.copy(inProgress = false) + } else { + composeState.value = ComposeState(useLinkPreviews = useLinkPreviews) + resetLinkPreview() + } + recState.value = RecordingState.NotStarted + textStyle.value = smallFont + chatModel.removeLiveDummy() + } + + fun deleteUnusedFiles() { + chatModel.filesToDelete.forEach { it.delete() } + chatModel.filesToDelete.clear() + } + + suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: String? = null, live: Boolean = false, ttl: Int?): ChatItem? { + val aChatItem = chatModel.controller.apiSendMessage( + type = cInfo.chatType, + id = cInfo.apiId, + file = file, + quotedItemId = quoted, + mc = mc, + live = live, + ttl = ttl + ) + if (aChatItem != null) { + chatModel.addChatItem(cInfo, aChatItem.chatItem) + return aChatItem.chatItem + } + if (file != null) removeFile(file) + return null + } + + suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? { + val cInfo = chat.chatInfo + val cs = composeState.value + var sent: ChatItem? + val msgText = text ?: cs.message + + fun sending() { + composeState.value = composeState.value.copy(inProgress = true) + } + + fun checkLinkPreview(): MsgContent { + return when (val composePreview = cs.preview) { + is ComposePreview.CLinkPreview -> { + val url = parseMessage(msgText) + val lp = composePreview.linkPreview + if (lp != null && url == lp.uri) { + MsgContent.MCLink(msgText, preview = lp) + } else { + MsgContent.MCText(msgText) + } + } + else -> MsgContent.MCText(msgText) + } + } + + fun updateMsgContent(msgContent: MsgContent): MsgContent { + return when (msgContent) { + is MsgContent.MCText -> checkLinkPreview() + is MsgContent.MCLink -> checkLinkPreview() + is MsgContent.MCImage -> MsgContent.MCImage(msgText, image = msgContent.image) + is MsgContent.MCVideo -> MsgContent.MCVideo(msgText, image = msgContent.image, duration = msgContent.duration) + is MsgContent.MCVoice -> MsgContent.MCVoice(msgText, duration = msgContent.duration) + is MsgContent.MCFile -> MsgContent.MCFile(msgText) + is MsgContent.MCUnknown -> MsgContent.MCUnknown(type = msgContent.type, text = msgText, json = msgContent.json) + } + } + + suspend fun updateMessage(ei: ChatItem, cInfo: ChatInfo, live: Boolean): ChatItem? { + val oldMsgContent = ei.content.msgContent + if (oldMsgContent != null) { + val updatedItem = chatModel.controller.apiUpdateChatItem( + type = cInfo.chatType, + id = cInfo.apiId, + itemId = ei.meta.itemId, + mc = updateMsgContent(oldMsgContent), + live = live + ) + if (updatedItem != null) chatModel.upsertChatItem(cInfo, updatedItem.chatItem) + return updatedItem?.chatItem + } + return null + } + + val liveMessage = cs.liveMessage + if (!live) { + if (liveMessage != null) composeState.value = cs.copy(liveMessage = null) + sending() + } + + if (cs.contextItem is ComposeContextItem.EditingItem) { + val ei = cs.contextItem.chatItem + sent = updateMessage(ei, cInfo, live) + } else if (liveMessage != null && liveMessage.sent) { + sent = updateMessage(liveMessage.chatItem, cInfo, live) + } else { + val msgs: ArrayList = ArrayList() + val files: ArrayList = ArrayList() + when (val preview = cs.preview) { + ComposePreview.NoPreview -> msgs.add(MsgContent.MCText(msgText)) + is ComposePreview.CLinkPreview -> msgs.add(checkLinkPreview()) + is ComposePreview.MediaPreview -> { + preview.content.forEachIndexed { index, it -> + val file = when (it) { + is UploadContent.SimpleImage -> saveImage(it.uri) + is UploadContent.AnimatedImage -> saveAnimImage(it.uri) + is UploadContent.Video -> saveFileFromUri(it.uri) + } + if (file != null) { + files.add(file) + if (it is UploadContent.Video) { + msgs.add(MsgContent.MCVideo(if (preview.content.lastIndex == index) msgText else "", preview.images[index], it.duration)) + } else { + msgs.add(MsgContent.MCImage(if (preview.content.lastIndex == index) msgText else "", preview.images[index])) + } + } + } + } + is ComposePreview.VoicePreview -> { + val tmpFile = File(preview.voice) + AudioPlayer.stop(tmpFile.absolutePath) + val actualFile = File(getAppFilePath(tmpFile.name.replaceAfter(RecorderNative.extension, ""))) + withContext(Dispatchers.IO) { + Files.move(tmpFile.toPath(), actualFile.toPath()) + } + files.add(actualFile.name) + deleteUnusedFiles() + msgs.add(MsgContent.MCVoice(if (msgs.isEmpty()) msgText else "", preview.durationMs / 1000)) + } + is ComposePreview.FilePreview -> { + val file = saveFileFromUri(preview.uri) + if (file != null) { + files.add((file)) + msgs.add(MsgContent.MCFile(if (msgs.isEmpty()) msgText else "")) + } + } + } + val quotedItemId: Long? = when (cs.contextItem) { + is ComposeContextItem.QuotedItem -> cs.contextItem.chatItem.id + else -> null + } + sent = null + msgs.forEachIndexed { index, content -> + if (index > 0) delay(100) + sent = send(cInfo, content, if (index == 0) quotedItemId else null, files.getOrNull(index), + live = if (content !is MsgContent.MCVoice && index == msgs.lastIndex) live else false, + ttl = ttl + ) + } + if (sent == null && + (cs.preview is ComposePreview.MediaPreview || + cs.preview is ComposePreview.FilePreview || + cs.preview is ComposePreview.VoicePreview) + ) { + sent = send(cInfo, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) + } + } + clearState(live) + return sent + } + + fun sendMessage(ttl: Int?) { + withBGApi { + sendMessageAsync(null, false, ttl) + } + } + + fun onMessageChange(s: String) { + composeState.value = composeState.value.copy(message = s) + if (isShortEmoji(s)) { + textStyle.value = if (s.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont + } else { + textStyle.value = smallFont + if (composeState.value.linkPreviewAllowed) { + if (s.isNotEmpty()) showLinkPreview(s) + else resetLinkPreview() + } + } + } + + fun onAudioAdded(filePath: String, durationMs: Int, finished: Boolean) { + val file = File(filePath) + chatModel.filesToDelete.add(file) + composeState.value = composeState.value.copy(preview = ComposePreview.VoicePreview(filePath, durationMs, finished)) + } + + fun allowVoiceToContact() { + val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return + withApi { + chatModel.controller.allowFeatureToContact(contact, ChatFeature.Voice) + } + } + + fun cancelLinkPreview() { + val uri = composeState.value.linkPreview?.uri + if (uri != null) { + cancelledLinks.add(uri) + } + pendingLinkUrl.value = null + composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) + } + + fun cancelImages() { + composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) + } + + fun cancelVoice() { + val filePath = recState.value.filePathNullable + recState.value = RecordingState.NotStarted + composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) + withBGApi { + RecorderNative.stopRecording?.invoke() + AudioPlayer.stop(filePath) + filePath?.let { File(it).delete() } + } + } + + fun cancelFile() { + composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) + } + + fun truncateToWords(s: String): String { + var acc = "" + val word = StringBuilder() + for (c in s) { + if (c.isLetter() || c.isDigit()) { + word.append(c) + } else { + acc = acc + word.toString() + c + word.clear() + } + } + return acc + } + + suspend fun sendLiveMessage() { + val cs = composeState.value + val typedMsg = cs.message + if ((cs.sendEnabled() || cs.contextItem is ComposeContextItem.QuotedItem) && (cs.liveMessage == null || !cs.liveMessage.sent)) { + val ci = sendMessageAsync(typedMsg, live = true, ttl = null) + if (ci != null) { + composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = typedMsg, sent = true)) + } + } else if (cs.liveMessage == null) { + val cItem = chatModel.addLiveDummy(chat.chatInfo) + composeState.value = composeState.value.copy(liveMessage = LiveMessage(cItem, typedMsg = typedMsg, sentMsg = typedMsg, sent = false)) + } + } + + fun liveMessageToSend(lm: LiveMessage, t: String): String? { + val s = if (t != lm.typedMsg) truncateToWords(t) else t + return if (s != lm.sentMsg) s else null + } + + suspend fun updateLiveMessage() { + val typedMsg = composeState.value.message + val liveMessage = composeState.value.liveMessage + if (liveMessage != null) { + val sentMsg = liveMessageToSend(liveMessage, typedMsg) + if (sentMsg != null) { + val ci = sendMessageAsync(sentMsg, live = true, ttl = null) + if (ci != null) { + composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true)) + } + } else if (liveMessage.typedMsg != typedMsg) { + composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg)) + } + } + } + + @Composable + fun previewView() { + when (val preview = composeState.value.preview) { + ComposePreview.NoPreview -> {} + is ComposePreview.CLinkPreview -> ComposeLinkView( + preview.linkPreview, + ::cancelLinkPreview, + cancelEnabled = !composeState.value.inProgress + ) + is ComposePreview.MediaPreview -> ComposeImageView( + preview, + ::cancelImages, + cancelEnabled = !composeState.value.editing && !composeState.value.inProgress + ) + is ComposePreview.VoicePreview -> ComposeVoiceView( + preview.voice, + preview.durationMs, + preview.finished, + cancelEnabled = !composeState.value.editing && !composeState.value.inProgress, + ::cancelVoice + ) + is ComposePreview.FilePreview -> ComposeFileView( + preview.fileName, + ::cancelFile, + cancelEnabled = !composeState.value.editing && !composeState.value.inProgress + ) + } + } + + @Composable + fun contextItemView() { + when (val contextItem = composeState.value.contextItem) { + ComposeContextItem.NoContextItem -> {} + is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_reply)) { + composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem) + } + is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_edit_filled)) { + clearState() + } + } + } + + // In case a user sent something, state is in progress, the user rotates a screen to different orientation. + // Without clearing the state the user will be unable to send anything until re-enters ChatView + LaunchedEffect(Unit) { + if (composeState.value.inProgress) { + clearState() + } + } + + LaunchedEffect(chatModel.sharedContent.value) { + // Important. If it's null, don't do anything, chat is not closed yet but will be after a moment + if (chatModel.chatId.value == null) return@LaunchedEffect + + when (val shared = chatModel.sharedContent.value) { + is SharedContent.Text -> onMessageChange(shared.text) + is SharedContent.Media -> processPickedMedia(shared.uris, shared.text) + is SharedContent.File -> processPickedFile(shared.uri, shared.text) + null -> {} + } + chatModel.sharedContent.value = null + } + + val userCanSend = rememberUpdatedState(chat.userCanSend) + val userIsObserver = rememberUpdatedState(chat.userIsObserver) + + Column { + if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) { + contextItemView() + when { + composeState.value.editing && composeState.value.preview is ComposePreview.VoicePreview -> {} + composeState.value.editing && composeState.value.preview is ComposePreview.FilePreview -> {} + else -> previewView() + } + } else { + Box { + Box(Modifier.align(Alignment.TopStart).padding(bottom = 69.dp)) { + contextItemView() + } + Box(Modifier.align(Alignment.BottomStart)) { + previewView() + } + } + } + Row( + modifier = Modifier.padding(end = 8.dp), + verticalAlignment = Alignment.Bottom, + ) { + val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on + val attachmentClicked = if (isGroupAndProhibitedFiles) { + { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.files_and_media_prohibited), + text = generalGetString(MR.strings.only_owners_can_enable_files_and_media) + ) + } + } else { + showChooseAttachment + } + IconButton(attachmentClicked, enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value) { + Icon( + painterResource(MR.images.ic_attach_file_filled_500), + contentDescription = stringResource(MR.strings.attach), + tint = if (!composeState.value.attachmentDisabled && userCanSend.value && !isGroupAndProhibitedFiles) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + ) + } + val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Voice) } + LaunchedEffect(allowedVoiceByPrefs) { + if (!allowedVoiceByPrefs && composeState.value.preview is ComposePreview.VoicePreview) { + // Voice was disabled right when this user records it, just cancel it + cancelVoice() + } + } + val needToAllowVoiceToContact = remember(chat.chatInfo) { + chat.chatInfo is ChatInfo.Direct && with(chat.chatInfo.contact.mergedPreferences.voice) { + ((userPreference as? ContactUserPref.User)?.preference?.allow == FeatureAllowed.NO || (userPreference as? ContactUserPref.Contact)?.preference?.allow == FeatureAllowed.NO) && + contactPreference.allow == FeatureAllowed.YES + } + } + LaunchedEffect(Unit) { + snapshotFlow { recState.value } + .distinctUntilChanged() + .collect { + when(it) { + is RecordingState.Started -> onAudioAdded(it.filePath, it.progressMs, false) + is RecordingState.Finished -> onAudioAdded(it.filePath, it.durationMs, true) + is RecordingState.NotStarted -> {} + } + } + } + + fun clearCurrentDraft() { + if (chatModel.draftChatId.value == chat.id) { + chatModel.draft.value = null + chatModel.draftChatId.value = null + } + } + + LaunchedEffect(rememberUpdatedState(chat.userCanSend).value) { + if (!chat.userCanSend) { + clearCurrentDraft() + clearState() + } + } + + val activity = LocalContext.current as Activity + DisposableEffect(Unit) { + val orientation = activity.resources.configuration.orientation + onDispose { + if (orientation == activity.resources.configuration.orientation) { + val cs = composeState.value + if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) { + sendMessage(null) + resetLinkPreview() + clearCurrentDraft() + deleteUnusedFiles() + } else if (composeState.value.inProgress) { + clearCurrentDraft() + } else if (!composeState.value.empty) { + if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) { + composeState.value = cs.copy(preview = cs.preview.copy(finished = true)) + } + chatModel.draft.value = composeState.value + chatModel.draftChatId.value = chat.id + } else { + clearCurrentDraft() + deleteUnusedFiles() + } + chatModel.removeLiveDummy() + } + } + } + + val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) } + SendMsgView( + composeState, + showVoiceRecordIcon = true, + recState, + chat.chatInfo is ChatInfo.Direct, + liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown, + needToAllowVoiceToContact, + allowedVoiceByPrefs, + allowVoiceToContact = ::allowVoiceToContact, + userIsObserver = userIsObserver.value, + userCanSend = userCanSend.value, + timedMessageAllowed = timedMessageAllowed, + customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime, + sendMessage = { ttl -> + sendMessage(ttl) + resetLinkPreview() + }, + sendLiveMessage = ::sendLiveMessage, + updateLiveMessage = ::updateLiveMessage, + cancelLiveMessage = { + composeState.value = composeState.value.copy(liveMessage = null) + chatModel.removeLiveDummy() + }, + onMessageChange = ::onMessageChange, + textStyle = textStyle + ) + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt index f0569c6661..a8223582ae 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt @@ -1,186 +1,26 @@ -@file:UseSerializers(UriSerializer::class) package chat.simplex.app.views.chat -import ComposeFileView -import ComposeVoiceView import android.Manifest -import android.app.Activity -import android.content.* +import android.content.ActivityNotFoundException import android.content.pm.PackageManager -import android.graphics.* -import android.graphics.drawable.AnimatedImageDrawable +import android.graphics.Bitmap import android.net.Uri -import android.os.Build -import android.provider.MediaStore -import android.webkit.MimeTypeMap import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContract -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.views.chat.item.* +import chat.simplex.app.SimplexApp +import chat.simplex.app.platform.resizeImageToStrSize import chat.simplex.app.views.helpers.* import chat.simplex.res.MR -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.serialization.* -import java.io.File -import java.nio.file.Files - -@Serializable -sealed class ComposePreview { - @Serializable object NoPreview: ComposePreview() - @Serializable class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview() - @Serializable class MediaPreview(val images: List, val content: List): ComposePreview() - @Serializable data class VoicePreview(val voice: String, val durationMs: Int, val finished: Boolean): ComposePreview() - @Serializable class FilePreview(val fileName: String, val uri: Uri): ComposePreview() -} - -@Serializable -sealed class ComposeContextItem { - @Serializable object NoContextItem: ComposeContextItem() - @Serializable class QuotedItem(val chatItem: ChatItem): ComposeContextItem() - @Serializable class EditingItem(val chatItem: ChatItem): ComposeContextItem() -} - -@Serializable -data class LiveMessage( - val chatItem: ChatItem, - val typedMsg: String, - val sentMsg: String, - val sent: Boolean -) - -@Serializable -data class ComposeState( - val message: String = "", - val liveMessage: LiveMessage? = null, - val preview: ComposePreview = ComposePreview.NoPreview, - val contextItem: ComposeContextItem = ComposeContextItem.NoContextItem, - val inProgress: Boolean = false, - val useLinkPreviews: Boolean -) { - constructor(editingItem: ChatItem, liveMessage: LiveMessage? = null, useLinkPreviews: Boolean): this( - editingItem.content.text, - liveMessage, - chatItemPreview(editingItem), - ComposeContextItem.EditingItem(editingItem), - useLinkPreviews = useLinkPreviews - ) - - val editing: Boolean - get() = - when (contextItem) { - is ComposeContextItem.EditingItem -> true - else -> false - } - val sendEnabled: () -> Boolean - get() = { - val hasContent = when (preview) { - is ComposePreview.MediaPreview -> true - is ComposePreview.VoicePreview -> true - is ComposePreview.FilePreview -> true - else -> message.isNotEmpty() || liveMessage != null - } - hasContent && !inProgress - } - val endLiveDisabled: Boolean - get() = liveMessage != null && message.isEmpty() && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem - - val linkPreviewAllowed: Boolean - get() = - when (preview) { - is ComposePreview.MediaPreview -> false - is ComposePreview.VoicePreview -> false - is ComposePreview.FilePreview -> false - else -> useLinkPreviews - } - val linkPreview: LinkPreview? - get() = - when (preview) { - is ComposePreview.CLinkPreview -> preview.linkPreview - else -> null - } - - val attachmentDisabled: Boolean - get() { - if (editing || liveMessage != null || inProgress) return true - return when (preview) { - ComposePreview.NoPreview -> false - is ComposePreview.CLinkPreview -> false - else -> true - } - } - - val empty: Boolean - get() = message.isEmpty() && preview is ComposePreview.NoPreview - - companion object { - fun saver(): Saver, *> = Saver( - save = { json.encodeToString(serializer(), it.value) }, - restore = { - mutableStateOf(json.decodeFromString(it)) - } - ) - } -} - -sealed class RecordingState { - object NotStarted: RecordingState() - class Started(val filePath: String, val progressMs: Int = 0): RecordingState() - class Finished(val filePath: String, val durationMs: Int): RecordingState() - - val filePathNullable: String? - get() = (this as? Started)?.filePath -} - -fun chatItemPreview(chatItem: ChatItem): ComposePreview { - val fileName = chatItem.file?.fileName ?: "" - return when (val mc = chatItem.content.msgContent) { - is MsgContent.MCText -> ComposePreview.NoPreview - is MsgContent.MCLink -> ComposePreview.CLinkPreview(linkPreview = mc.preview) - // TODO: include correct type - is MsgContent.MCImage -> ComposePreview.MediaPreview(images = listOf(mc.image), listOf(UploadContent.SimpleImage(getAppFileUri(fileName)))) - is MsgContent.MCVideo -> ComposePreview.MediaPreview(images = listOf(mc.image), listOf(UploadContent.SimpleImage(getAppFileUri(fileName)))) - is MsgContent.MCVoice -> ComposePreview.VoicePreview(voice = fileName, mc.duration / 1000, true) - is MsgContent.MCFile -> ComposePreview.FilePreview(fileName, getAppFileUri(fileName)) - is MsgContent.MCUnknown, null -> ComposePreview.NoPreview - } -} @Composable -fun ComposeView( - chatModel: ChatModel, - chat: Chat, +fun AttachmentSelection( composeState: MutableState, attachmentOption: MutableState, - showChooseAttachment: () -> Unit + processPickedFile: (Uri?, String?) -> Unit, + processPickedMedia: (List, String?) -> Unit ) { - val context = LocalContext.current - val linkUrl = rememberSaveable { mutableStateOf(null) } - val prevLinkUrl = rememberSaveable { mutableStateOf(null) } - val pendingLinkUrl = rememberSaveable { mutableStateOf(null) } - val cancelledLinks = rememberSaveable { mutableSetOf() } - val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() - val maxFileSize = getMaxFileSize(FileProtocol.XFTP) - val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) - val textStyle = remember { mutableStateOf(smallFont) } val cameraLauncher = rememberCameraLauncher { uri: Uri? -> if (uri != null) { val bitmap: Bitmap? = getBitmapFromUri(uri) @@ -194,69 +34,7 @@ fun ComposeView( if (isGranted) { cameraLauncher.launchWithFallback() } else { - Toast.makeText(context, generalGetString(MR.strings.toast_permission_denied), Toast.LENGTH_SHORT).show() - } - } - val processPickedMedia = { uris: List, text: String? -> - val content = ArrayList() - val imagesPreview = ArrayList() - uris.forEach { uri -> - var bitmap: Bitmap? = null - val isImage = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileName(uri)?.split(".")?.last())?.contains("image/") == true - when { - isImage -> { - // Image - val drawable = getDrawableFromUri(uri) - bitmap = if (drawable != null) getBitmapFromUri(uri) else null - val isAnimNewApi = Build.VERSION.SDK_INT >= 28 && drawable is AnimatedImageDrawable - val isAnimOldApi = Build.VERSION.SDK_INT < 28 && - (getFileName(uri)?.endsWith(".gif") == true || getFileName(uri)?.endsWith(".webp") == true) - if (isAnimNewApi || isAnimOldApi) { - // It's a gif or webp - val fileSize = getFileSize(uri) - if (fileSize != null && fileSize <= maxFileSize) { - content.add(UploadContent.AnimatedImage(uri)) - } else { - bitmap = null - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.large_file), - String.format(generalGetString(MR.strings.maximum_supported_file_size), formatBytes(maxFileSize)) - ) - } - } else { - content.add(UploadContent.SimpleImage(uri)) - } - } - else -> { - // Video - val res = getBitmapFromVideo(uri) - bitmap = res.preview - val durationMs = res.duration - content.add(UploadContent.Video(uri, durationMs?.div(1000)?.toInt() ?: 0)) - } - } - if (bitmap != null) { - imagesPreview.add(resizeImageToStrSize(bitmap, maxDataSize = 14000)) - } - } - if (imagesPreview.isNotEmpty()) { - composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.MediaPreview(imagesPreview, content)) - } - } - val processPickedFile = { uri: Uri?, text: String? -> - if (uri != null) { - val fileSize = getFileSize(uri) - if (fileSize != null && fileSize <= maxFileSize) { - val fileName = getFileName(uri) - if (fileName != null) { - composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.FilePreview(fileName, uri)) - } - } else { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.large_file), - String.format(generalGetString(MR.strings.maximum_supported_file_size), formatBytes(maxFileSize)) - ) - } + Toast.makeText(SimplexApp.context, generalGetString(MR.strings.toast_permission_denied), Toast.LENGTH_SHORT).show() } } val galleryImageLauncher = rememberLauncherForActivityResult(contract = PickMultipleImagesFromGallery()) { processPickedMedia(it, null) } @@ -265,13 +43,11 @@ fun ComposeView( val galleryVideoLauncherFallback = rememberGetMultipleContentsLauncher { processPickedMedia(it, null) } val filesLauncher = rememberGetContentLauncher { processPickedFile(it, null) } - val recState: MutableState = remember { mutableStateOf(RecordingState.NotStarted) } - LaunchedEffect(attachmentOption.value) { when (attachmentOption.value) { AttachmentOption.CameraPhoto -> { when (PackageManager.PERMISSION_GRANTED) { - ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) -> { + ContextCompat.checkSelfPermission(SimplexApp.context, Manifest.permission.CAMERA) -> { cameraLauncher.launchWithFallback() } else -> { @@ -303,595 +79,4 @@ fun ComposeView( else -> {} } } - - fun isSimplexLink(link: String): Boolean = - link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true) - - fun parseMessage(msg: String): String? { - val parsedMsg = runBlocking { chatModel.controller.apiParseMarkdown(msg) } - val link = parsedMsg?.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) } - return link?.text - } - - fun loadLinkPreview(url: String, wait: Long? = null) { - if (pendingLinkUrl.value == url) { - composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null)) - withApi { - if (wait != null) delay(wait) - val lp = getLinkPreview(url) - if (lp != null && pendingLinkUrl.value == url) { - composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(lp)) - pendingLinkUrl.value = null - } else if (pendingLinkUrl.value == url) { - composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) - pendingLinkUrl.value = null - } - } - } - } - - fun showLinkPreview(s: String) { - prevLinkUrl.value = linkUrl.value - linkUrl.value = parseMessage(s) - val url = linkUrl.value - if (url != null) { - if (url != composeState.value.linkPreview?.uri && url != pendingLinkUrl.value) { - pendingLinkUrl.value = url - loadLinkPreview(url, wait = if (prevLinkUrl.value == url) null else 1500L) - } - } else { - composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) - } - } - - fun resetLinkPreview() { - linkUrl.value = null - prevLinkUrl.value = null - pendingLinkUrl.value = null - cancelledLinks.clear() - } - - fun clearState(live: Boolean = false) { - if (live) { - composeState.value = composeState.value.copy(inProgress = false) - } else { - composeState.value = ComposeState(useLinkPreviews = useLinkPreviews) - resetLinkPreview() - } - recState.value = RecordingState.NotStarted - textStyle.value = smallFont - chatModel.removeLiveDummy() - } - - fun deleteUnusedFiles() { - chatModel.filesToDelete.forEach { it.delete() } - chatModel.filesToDelete.clear() - } - - suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: String? = null, live: Boolean = false, ttl: Int?): ChatItem? { - val aChatItem = chatModel.controller.apiSendMessage( - type = cInfo.chatType, - id = cInfo.apiId, - file = file, - quotedItemId = quoted, - mc = mc, - live = live, - ttl = ttl - ) - if (aChatItem != null) { - chatModel.addChatItem(cInfo, aChatItem.chatItem) - return aChatItem.chatItem - } - if (file != null) removeFile(file) - return null - } - - - - suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? { - val cInfo = chat.chatInfo - val cs = composeState.value - var sent: ChatItem? - val msgText = text ?: cs.message - - fun sending() { - composeState.value = composeState.value.copy(inProgress = true) - } - - fun checkLinkPreview(): MsgContent { - return when (val composePreview = cs.preview) { - is ComposePreview.CLinkPreview -> { - val url = parseMessage(msgText) - val lp = composePreview.linkPreview - if (lp != null && url == lp.uri) { - MsgContent.MCLink(msgText, preview = lp) - } else { - MsgContent.MCText(msgText) - } - } - else -> MsgContent.MCText(msgText) - } - } - - fun updateMsgContent(msgContent: MsgContent): MsgContent { - return when (msgContent) { - is MsgContent.MCText -> checkLinkPreview() - is MsgContent.MCLink -> checkLinkPreview() - is MsgContent.MCImage -> MsgContent.MCImage(msgText, image = msgContent.image) - is MsgContent.MCVideo -> MsgContent.MCVideo(msgText, image = msgContent.image, duration = msgContent.duration) - is MsgContent.MCVoice -> MsgContent.MCVoice(msgText, duration = msgContent.duration) - is MsgContent.MCFile -> MsgContent.MCFile(msgText) - is MsgContent.MCUnknown -> MsgContent.MCUnknown(type = msgContent.type, text = msgText, json = msgContent.json) - } - } - - suspend fun updateMessage(ei: ChatItem, cInfo: ChatInfo, live: Boolean): ChatItem? { - val oldMsgContent = ei.content.msgContent - if (oldMsgContent != null) { - val updatedItem = chatModel.controller.apiUpdateChatItem( - type = cInfo.chatType, - id = cInfo.apiId, - itemId = ei.meta.itemId, - mc = updateMsgContent(oldMsgContent), - live = live - ) - if (updatedItem != null) chatModel.upsertChatItem(cInfo, updatedItem.chatItem) - return updatedItem?.chatItem - } - return null - } - - val liveMessage = cs.liveMessage - if (!live) { - if (liveMessage != null) composeState.value = cs.copy(liveMessage = null) - sending() - } - - if (cs.contextItem is ComposeContextItem.EditingItem) { - val ei = cs.contextItem.chatItem - sent = updateMessage(ei, cInfo, live) - } else if (liveMessage != null && liveMessage.sent) { - sent = updateMessage(liveMessage.chatItem, cInfo, live) - } else { - val msgs: ArrayList = ArrayList() - val files: ArrayList = ArrayList() - when (val preview = cs.preview) { - ComposePreview.NoPreview -> msgs.add(MsgContent.MCText(msgText)) - is ComposePreview.CLinkPreview -> msgs.add(checkLinkPreview()) - is ComposePreview.MediaPreview -> { - preview.content.forEachIndexed { index, it -> - val file = when (it) { - is UploadContent.SimpleImage -> saveImage(it.uri) - is UploadContent.AnimatedImage -> saveAnimImage(it.uri) - is UploadContent.Video -> saveFileFromUri(it.uri) - } - if (file != null) { - files.add(file) - if (it is UploadContent.Video) { - msgs.add(MsgContent.MCVideo(if (preview.content.lastIndex == index) msgText else "", preview.images[index], it.duration)) - } else { - msgs.add(MsgContent.MCImage(if (preview.content.lastIndex == index) msgText else "", preview.images[index])) - } - } - } - } - is ComposePreview.VoicePreview -> { - val tmpFile = File(preview.voice) - AudioPlayer.stop(tmpFile.absolutePath) - val actualFile = File(getAppFilePath(tmpFile.name.replaceAfter(RecorderNative.extension, ""))) - withContext(Dispatchers.IO) { - Files.move(tmpFile.toPath(), actualFile.toPath()) - } - files.add(actualFile.name) - deleteUnusedFiles() - msgs.add(MsgContent.MCVoice(if (msgs.isEmpty()) msgText else "", preview.durationMs / 1000)) - } - is ComposePreview.FilePreview -> { - val file = saveFileFromUri(preview.uri) - if (file != null) { - files.add((file)) - msgs.add(MsgContent.MCFile(if (msgs.isEmpty()) msgText else "")) - } - } - } - val quotedItemId: Long? = when (cs.contextItem) { - is ComposeContextItem.QuotedItem -> cs.contextItem.chatItem.id - else -> null - } - sent = null - msgs.forEachIndexed { index, content -> - if (index > 0) delay(100) - sent = send(cInfo, content, if (index == 0) quotedItemId else null, files.getOrNull(index), - live = if (content !is MsgContent.MCVoice && index == msgs.lastIndex) live else false, - ttl = ttl - ) - } - if (sent == null && - (cs.preview is ComposePreview.MediaPreview || - cs.preview is ComposePreview.FilePreview || - cs.preview is ComposePreview.VoicePreview) - ) { - sent = send(cInfo, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) - } - } - clearState(live) - return sent - } - - fun sendMessage(ttl: Int?) { - withBGApi { - sendMessageAsync(null, false, ttl) - } - } - - fun onMessageChange(s: String) { - composeState.value = composeState.value.copy(message = s) - if (isShortEmoji(s)) { - textStyle.value = if (s.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont - } else { - textStyle.value = smallFont - if (composeState.value.linkPreviewAllowed) { - if (s.isNotEmpty()) showLinkPreview(s) - else resetLinkPreview() - } - } - } - - fun onAudioAdded(filePath: String, durationMs: Int, finished: Boolean) { - val file = File(filePath) - chatModel.filesToDelete.add(file) - composeState.value = composeState.value.copy(preview = ComposePreview.VoicePreview(filePath, durationMs, finished)) - } - - fun allowVoiceToContact() { - val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return - withApi { - chatModel.controller.allowFeatureToContact(contact, ChatFeature.Voice) - } - } - - fun cancelLinkPreview() { - val uri = composeState.value.linkPreview?.uri - if (uri != null) { - cancelledLinks.add(uri) - } - pendingLinkUrl.value = null - composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) - } - - fun cancelImages() { - composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) - } - - fun cancelVoice() { - val filePath = recState.value.filePathNullable - recState.value = RecordingState.NotStarted - composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) - withBGApi { - RecorderNative.stopRecording?.invoke() - AudioPlayer.stop(filePath) - filePath?.let { File(it).delete() } - } - } - - fun cancelFile() { - composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) - } - - fun truncateToWords(s: String): String { - var acc = "" - val word = StringBuilder() - for (c in s) { - if (c.isLetter() || c.isDigit()) { - word.append(c) - } else { - acc = acc + word.toString() + c - word.clear() - } - } - return acc - } - - suspend fun sendLiveMessage() { - val cs = composeState.value - val typedMsg = cs.message - if ((cs.sendEnabled() || cs.contextItem is ComposeContextItem.QuotedItem) && (cs.liveMessage == null || !cs.liveMessage.sent)) { - val ci = sendMessageAsync(typedMsg, live = true, ttl = null) - if (ci != null) { - composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = typedMsg, sent = true)) - } - } else if (cs.liveMessage == null) { - val cItem = chatModel.addLiveDummy(chat.chatInfo) - composeState.value = composeState.value.copy(liveMessage = LiveMessage(cItem, typedMsg = typedMsg, sentMsg = typedMsg, sent = false)) - } - } - - fun liveMessageToSend(lm: LiveMessage, t: String): String? { - val s = if (t != lm.typedMsg) truncateToWords(t) else t - return if (s != lm.sentMsg) s else null - } - - suspend fun updateLiveMessage() { - val typedMsg = composeState.value.message - val liveMessage = composeState.value.liveMessage - if (liveMessage != null) { - val sentMsg = liveMessageToSend(liveMessage, typedMsg) - if (sentMsg != null) { - val ci = sendMessageAsync(sentMsg, live = true, ttl = null) - if (ci != null) { - composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true)) - } - } else if (liveMessage.typedMsg != typedMsg) { - composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg)) - } - } - } - - @Composable - fun previewView() { - when (val preview = composeState.value.preview) { - ComposePreview.NoPreview -> {} - is ComposePreview.CLinkPreview -> ComposeLinkView( - preview.linkPreview, - ::cancelLinkPreview, - cancelEnabled = !composeState.value.inProgress - ) - is ComposePreview.MediaPreview -> ComposeImageView( - preview, - ::cancelImages, - cancelEnabled = !composeState.value.editing && !composeState.value.inProgress - ) - is ComposePreview.VoicePreview -> ComposeVoiceView( - preview.voice, - preview.durationMs, - preview.finished, - cancelEnabled = !composeState.value.editing && !composeState.value.inProgress, - ::cancelVoice - ) - is ComposePreview.FilePreview -> ComposeFileView( - preview.fileName, - ::cancelFile, - cancelEnabled = !composeState.value.editing && !composeState.value.inProgress - ) - } - } - - @Composable - fun contextItemView() { - when (val contextItem = composeState.value.contextItem) { - ComposeContextItem.NoContextItem -> {} - is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_reply)) { - composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem) - } - is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_edit_filled)) { - clearState() - } - } - } - - // In case a user sent something, state is in progress, the user rotates a screen to different orientation. - // Without clearing the state the user will be unable to send anything until re-enters ChatView - LaunchedEffect(Unit) { - if (composeState.value.inProgress) { - clearState() - } - } - - LaunchedEffect(chatModel.sharedContent.value) { - // Important. If it's null, don't do anything, chat is not closed yet but will be after a moment - if (chatModel.chatId.value == null) return@LaunchedEffect - - when (val shared = chatModel.sharedContent.value) { - is SharedContent.Text -> onMessageChange(shared.text) - is SharedContent.Media -> processPickedMedia(shared.uris, shared.text) - is SharedContent.File -> processPickedFile(shared.uri, shared.text) - null -> {} - } - chatModel.sharedContent.value = null - } - - val userCanSend = rememberUpdatedState(chat.userCanSend) - val userIsObserver = rememberUpdatedState(chat.userIsObserver) - - Column { - if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) { - contextItemView() - when { - composeState.value.editing && composeState.value.preview is ComposePreview.VoicePreview -> {} - composeState.value.editing && composeState.value.preview is ComposePreview.FilePreview -> {} - else -> previewView() - } - } else { - Box { - Box(Modifier.align(Alignment.TopStart).padding(bottom = 69.dp)) { - contextItemView() - } - Box(Modifier.align(Alignment.BottomStart)) { - previewView() - } - } - } - Row( - modifier = Modifier.padding(end = 8.dp), - verticalAlignment = Alignment.Bottom, - ) { - val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on - val attachmentClicked = if (isGroupAndProhibitedFiles) { - { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.files_and_media_prohibited), - text = generalGetString(MR.strings.only_owners_can_enable_files_and_media) - ) - } - } else { - showChooseAttachment - } - IconButton(attachmentClicked, enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value) { - Icon( - painterResource(MR.images.ic_attach_file_filled_500), - contentDescription = stringResource(MR.strings.attach), - tint = if (!composeState.value.attachmentDisabled && userCanSend.value && !isGroupAndProhibitedFiles) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - modifier = Modifier - .size(28.dp) - .clip(CircleShape) - ) - } - val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Voice) } - LaunchedEffect(allowedVoiceByPrefs) { - if (!allowedVoiceByPrefs && composeState.value.preview is ComposePreview.VoicePreview) { - // Voice was disabled right when this user records it, just cancel it - cancelVoice() - } - } - val needToAllowVoiceToContact = remember(chat.chatInfo) { - chat.chatInfo is ChatInfo.Direct && with(chat.chatInfo.contact.mergedPreferences.voice) { - ((userPreference as? ContactUserPref.User)?.preference?.allow == FeatureAllowed.NO || (userPreference as? ContactUserPref.Contact)?.preference?.allow == FeatureAllowed.NO) && - contactPreference.allow == FeatureAllowed.YES - } - } - LaunchedEffect(Unit) { - snapshotFlow { recState.value } - .distinctUntilChanged() - .collect { - when(it) { - is RecordingState.Started -> onAudioAdded(it.filePath, it.progressMs, false) - is RecordingState.Finished -> onAudioAdded(it.filePath, it.durationMs, true) - is RecordingState.NotStarted -> {} - } - } - } - - fun clearCurrentDraft() { - if (chatModel.draftChatId.value == chat.id) { - chatModel.draft.value = null - chatModel.draftChatId.value = null - } - } - - LaunchedEffect(rememberUpdatedState(chat.userCanSend).value) { - if (!chat.userCanSend) { - clearCurrentDraft() - clearState() - } - } - - val activity = LocalContext.current as Activity - DisposableEffect(Unit) { - val orientation = activity.resources.configuration.orientation - onDispose { - if (orientation == activity.resources.configuration.orientation) { - val cs = composeState.value - if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) { - sendMessage(null) - resetLinkPreview() - clearCurrentDraft() - deleteUnusedFiles() - } else if (composeState.value.inProgress) { - clearCurrentDraft() - } else if (!composeState.value.empty) { - if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) { - composeState.value = cs.copy(preview = cs.preview.copy(finished = true)) - } - chatModel.draft.value = composeState.value - chatModel.draftChatId.value = chat.id - } else { - clearCurrentDraft() - deleteUnusedFiles() - } - chatModel.removeLiveDummy() - } - } - } - - val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) } - SendMsgView( - composeState, - showVoiceRecordIcon = true, - recState, - chat.chatInfo is ChatInfo.Direct, - liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown, - needToAllowVoiceToContact, - allowedVoiceByPrefs, - allowVoiceToContact = ::allowVoiceToContact, - userIsObserver = userIsObserver.value, - userCanSend = userCanSend.value, - timedMessageAllowed = timedMessageAllowed, - customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime, - sendMessage = { ttl -> - sendMessage(ttl) - resetLinkPreview() - }, - sendLiveMessage = ::sendLiveMessage, - updateLiveMessage = ::updateLiveMessage, - cancelLiveMessage = { - composeState.value = composeState.value.copy(liveMessage = null) - chatModel.removeLiveDummy() - }, - onMessageChange = ::onMessageChange, - textStyle = textStyle - ) - } - } -} - -class PickFromGallery: ActivityResultContract() { - override fun createIntent(context: Context, input: Int) = - Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply { - type = "image/*" - } - - override fun parseResult(resultCode: Int, intent: Intent?): Uri? = intent?.data -} - -class PickMultipleImagesFromGallery: ActivityResultContract>() { - override fun createIntent(context: Context, input: Int) = - Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply { - putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) - type = "image/*" - } - - override fun parseResult(resultCode: Int, intent: Intent?): List = - if (intent?.data != null) - listOf(intent.data!!) - else if (intent?.clipData != null) - with(intent.clipData!!) { - val uris = ArrayList() - for (i in 0 until kotlin.math.min(itemCount, 10)) { - val uri = getItemAt(i).uri - if (uri != null) uris.add(uri) - } - if (itemCount > 10) { - AlertManager.shared.showAlertMsg(MR.strings.images_limit_title, MR.strings.images_limit_desc) - } - uris - } - else - emptyList() -} - - -class PickMultipleVideosFromGallery: ActivityResultContract>() { - override fun createIntent(context: Context, input: Int) = - Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI).apply { - putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) - type = "video/*" - } - - override fun parseResult(resultCode: Int, intent: Intent?): List = - if (intent?.data != null) - listOf(intent.data!!) - else if (intent?.clipData != null) - with(intent.clipData!!) { - val uris = ArrayList() - for (i in 0 until kotlin.math.min(itemCount, 10)) { - val uri = getItemAt(i).uri - if (uri != null) uris.add(uri) - } - if (itemCount > 10) { - AlertManager.shared.showAlertMsg(MR.strings.videos_limit_title, MR.strings.videos_limit_desc) - } - uris - } - else - emptyList() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt new file mode 100644 index 0000000000..a8f88d7ed8 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt @@ -0,0 +1,41 @@ +package chat.simplex.app.views.chat + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import chat.simplex.app.ui.theme.DEFAULT_PADDING +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.newchat.QRCodeScanner +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.stringResource + +@Composable +fun ScanCodeLayout(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { + Column( + Modifier + .fillMaxSize() + .padding(horizontal = DEFAULT_PADDING) + ) { + AppBarTitle(stringResource(MR.strings.scan_code), false) + Box( + Modifier + .fillMaxWidth() + .aspectRatio(ratio = 1F) + .padding(bottom = DEFAULT_PADDING) + ) { + QRCodeScanner { text -> + verifyCode(text) { + if (it) { + close() + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.incorrect_code) + ) + } + } + } + } + Text(stringResource(MR.strings.scan_code_from_contacts_app)) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt index 69304f8398..d7ef0895ac 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt @@ -1,18 +1,9 @@ package chat.simplex.app.views.chat import android.Manifest -import androidx.compose.foundation.layout.* -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.R -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCodeScanner import com.google.accompanist.permissions.rememberPermissionState -import chat.simplex.res.MR @Composable fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { @@ -22,33 +13,3 @@ fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () } ScanCodeLayout(verifyCode, close) } - -@Composable -private fun ScanCodeLayout(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { - Column( - Modifier - .fillMaxSize() - .padding(horizontal = DEFAULT_PADDING) - ) { - AppBarTitle(stringResource(MR.strings.scan_code), false) - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - .padding(bottom = DEFAULT_PADDING) - ) { - QRCodeScanner { text -> - verifyCode(text) { - if (it) { - close() - } else { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.incorrect_code) - ) - } - } - } - } - Text(stringResource(MR.strings.scan_code_from_contacts_app)) - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt new file mode 100644 index 0000000000..e644cfde1c --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt @@ -0,0 +1,653 @@ +package chat.simplex.app.views.chat + +import android.content.res.Configuration +import androidx.compose.animation.core.* +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.* +import androidx.compose.material.* +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.* +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.* +import androidx.compose.ui.window.Dialog +import chat.simplex.app.model.* +import chat.simplex.app.platform.LockToCurrentOrientationUntilDispose +import chat.simplex.app.platform.NativeKeyboard +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.chat.item.ItemAction +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.* + +@Composable +fun SendMsgView( + composeState: MutableState, + showVoiceRecordIcon: Boolean, + recState: MutableState, + isDirectChat: Boolean, + liveMessageAlertShown: SharedPreference, + needToAllowVoiceToContact: Boolean, + allowedVoiceByPrefs: Boolean, + userIsObserver: Boolean, + userCanSend: Boolean, + allowVoiceToContact: () -> Unit, + timedMessageAllowed: Boolean = false, + customDisappearingMessageTimePref: SharedPreference? = null, + sendMessage: (Int?) -> Unit, + sendLiveMessage: (suspend () -> Unit)? = null, + updateLiveMessage: (suspend () -> Unit)? = null, + cancelLiveMessage: (() -> Unit)? = null, + onMessageChange: (String) -> Unit, + textStyle: MutableState +) { + val showCustomDisappearingMessageDialog = remember { mutableStateOf(false) } + + if (showCustomDisappearingMessageDialog.value) { + CustomDisappearingMessageDialog( + sendMessage = sendMessage, + setShowDialog = { showCustomDisappearingMessageDialog.value = it }, + customDisappearingMessageTimePref = customDisappearingMessageTimePref + ) + } + + Box(Modifier.padding(vertical = 8.dp)) { + val cs = composeState.value + val showProgress = cs.inProgress && (cs.preview is ComposePreview.MediaPreview || cs.preview is ComposePreview.FilePreview) + val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing && + cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started) + val showDeleteTextButton = rememberSaveable { mutableStateOf(false) } + NativeKeyboard(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange) + // Disable clicks on text field + if (cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) { + Box( + Modifier + .matchParentSize() + .clickable(enabled = !userCanSend, indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.observer_cant_send_message_title), + text = generalGetString(MR.strings.observer_cant_send_message_desc) + ) + }) + ) + } + if (showDeleteTextButton.value) { + DeleteTextButton(composeState) + } + Box(Modifier.align(Alignment.BottomEnd)) { + val sendButtonSize = remember { Animatable(36f) } + val sendButtonAlpha = remember { Animatable(1f) } + val scope = rememberCoroutineScope() + LaunchedEffect(Unit) { + // Making LiveMessage alive when screen orientation was changed + if (cs.liveMessage != null && sendLiveMessage != null && updateLiveMessage != null) { + startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) + } + } + when { + showProgress -> ProgressIndicator() + showVoiceButton -> { + Row(verticalAlignment = Alignment.CenterVertically) { + val stopRecOnNextClick = remember { mutableStateOf(false) } + when { + needToAllowVoiceToContact || !allowedVoiceByPrefs || !userCanSend -> { + DisallowedVoiceButton(userCanSend) { + if (needToAllowVoiceToContact) { + showNeedToAllowVoiceAlert(allowVoiceToContact) + } else if (!allowedVoiceByPrefs) { + showDisabledVoiceAlert(isDirectChat) + } + } + } + !allowedToRecordVoiceByPlatform() -> + VoiceButtonWithoutPermissionByPlatform() + else -> + RecordVoiceView(recState, stopRecOnNextClick) + } + if (sendLiveMessage != null + && updateLiveMessage != null + && (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value) + && cs.contextItem is ComposeContextItem.NoContextItem + ) { + Spacer(Modifier.width(10.dp)) + StartLiveMessageButton(userCanSend) { + if (composeState.value.preview is ComposePreview.NoPreview) { + startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) + } + } + } + } + } + cs.liveMessage?.sent == false && cs.message.isEmpty() -> { + CancelLiveMessageButton { + cancelLiveMessage?.invoke() + } + } + else -> { + val cs = composeState.value + val icon = if (cs.editing || cs.liveMessage != null) painterResource(MR.images.ic_check_filled) else painterResource(MR.images.ic_arrow_upward) + val disabled = !cs.sendEnabled() || + (!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) || + cs.endLiveDisabled + val showDropdown = rememberSaveable { mutableStateOf(false) } + + @Composable + fun MenuItems(): List<@Composable () -> Unit> { + val menuItems = mutableListOf<@Composable () -> Unit>() + + if (cs.liveMessage == null && !cs.editing) { + if ( + cs.preview !is ComposePreview.VoicePreview && + cs.contextItem is ComposeContextItem.NoContextItem && + sendLiveMessage != null && updateLiveMessage != null + ) { + menuItems.add { + ItemAction( + generalGetString(MR.strings.send_live_message), + BoltFilled, + onClick = { + startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) + showDropdown.value = false + } + ) + } + } + if (timedMessageAllowed) { + menuItems.add { + ItemAction( + generalGetString(MR.strings.disappearing_message), + painterResource(MR.images.ic_timer), + onClick = { + showCustomDisappearingMessageDialog.value = true + showDropdown.value = false + } + ) + } + } + } + + return menuItems + } + + val menuItems = MenuItems() + if (menuItems.isNotEmpty()) { + SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage) { showDropdown.value = true } + DefaultDropdownMenu(showDropdown) { + menuItems.forEach { composable -> composable() } + } + } else { + SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage) + } + } + } + } + } +} + +@Composable +private fun CustomDisappearingMessageDialog( + sendMessage: (Int?) -> Unit, + setShowDialog: (Boolean) -> Unit, + customDisappearingMessageTimePref: SharedPreference? +) { + val showCustomTimePicker = remember { mutableStateOf(false) } + + if (showCustomTimePicker.value) { + val selectedDisappearingMessageTime = remember { + mutableStateOf(customDisappearingMessageTimePref?.get?.invoke() ?: 300) + } + CustomTimePickerDialog( + selectedDisappearingMessageTime, + title = generalGetString(MR.strings.delete_after), + confirmButtonText = generalGetString(MR.strings.send_disappearing_message_send), + confirmButtonAction = { ttl -> + sendMessage(ttl) + customDisappearingMessageTimePref?.set?.invoke(ttl) + setShowDialog(false) + }, + cancel = { setShowDialog(false) } + ) + } else { + @Composable + fun ChoiceButton( + text: String, + onClick: () -> Unit + ) { + TextButton(onClick) { + Text( + text, + fontSize = 18.sp, + color = MaterialTheme.colors.primary + ) + } + } + + Dialog(onDismissRequest = { setShowDialog(false) }) { + Surface( + shape = RoundedCornerShape(corner = CornerSize(25.dp)) + ) { + Box( + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.padding(DEFAULT_PADDING), + verticalArrangement = Arrangement.spacedBy(6.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(" ") // centers title + Text( + generalGetString(MR.strings.send_disappearing_message), + fontSize = 16.sp, + color = MaterialTheme.colors.secondary + ) + Icon( + painterResource(MR.images.ic_close), + generalGetString(MR.strings.icon_descr_close_button), + tint = MaterialTheme.colors.secondary, + modifier = Modifier + .size(25.dp) + .clickable { setShowDialog(false) } + ) + } + + ChoiceButton(generalGetString(MR.strings.send_disappearing_message_30_seconds)) { + sendMessage(30) + setShowDialog(false) + } + ChoiceButton(generalGetString(MR.strings.send_disappearing_message_1_minute)) { + sendMessage(60) + setShowDialog(false) + } + ChoiceButton(generalGetString(MR.strings.send_disappearing_message_5_minutes)) { + sendMessage(300) + setShowDialog(false) + } + ChoiceButton(generalGetString(MR.strings.send_disappearing_message_custom_time)) { + showCustomTimePicker.value = true + } + } + } + } + } + } +} + +@Composable +private fun BoxScope.DeleteTextButton(composeState: MutableState) { + IconButton( + { composeState.value = composeState.value.copy(message = "") }, + Modifier.align(Alignment.TopEnd).size(36.dp) + ) { + Icon(painterResource(MR.images.ic_close), null, Modifier.padding(7.dp).size(36.dp), tint = MaterialTheme.colors.secondary) + } +} + +@Composable +private fun RecordVoiceView(recState: MutableState, stopRecOnNextClick: MutableState) { + val rec: Recorder = remember { RecorderNative() } + DisposableEffect(Unit) { onDispose { rec.stop() } } + val stopRecordingAndAddAudio: () -> Unit = { + recState.value.filePathNullable?.let { + recState.value = RecordingState.Finished(it, rec.stop()) + } + } + if (stopRecOnNextClick.value) { + LaunchedEffect(recState.value) { + if (recState.value is RecordingState.NotStarted) { + stopRecOnNextClick.value = false + } + } + // Lock orientation to current orientation because screen rotation will break the recording + LockToCurrentOrientationUntilDispose() + StopRecordButton(stopRecordingAndAddAudio) + } else { + val startRecording: () -> Unit = { + recState.value = RecordingState.Started( + filePath = rec.start { progress: Int?, finished: Boolean -> + val state = recState.value + if (state is RecordingState.Started && progress != null) { + recState.value = if (!finished) + RecordingState.Started(state.filePath, progress) + else + RecordingState.Finished(state.filePath, progress) + } + }, + ) + } + val interactionSource = interactionSourceWithTapDetection( + onPress = { if (recState.value is RecordingState.NotStarted) startRecording() }, + onClick = { + if (stopRecOnNextClick.value) { + stopRecordingAndAddAudio() + } else { + // tapped and didn't hold a finger + stopRecOnNextClick.value = true + } + }, + onCancel = stopRecordingAndAddAudio, + onRelease = stopRecordingAndAddAudio + ) + RecordVoiceButton(interactionSource) + } +} + +@Composable +private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) { + IconButton(onClick, Modifier.size(36.dp), enabled = enabled) { + Icon( + painterResource(MR.images.ic_keyboard_voice), + stringResource(MR.strings.icon_descr_record_voice_message), + tint = MaterialTheme.colors.secondary, + modifier = Modifier + .size(36.dp) + .padding(4.dp) + ) + } +} + +@Composable +fun VoiceButtonWithoutPermission(onClick: () -> Unit) { + IconButton(onClick, Modifier.size(36.dp)) { + Icon( + painterResource(MR.images.ic_keyboard_voice_filled), + stringResource(MR.strings.icon_descr_record_voice_message), + tint = MaterialTheme.colors.primary, + modifier = Modifier + .size(34.dp) + .padding(4.dp) + ) + } +} + +@Composable +private fun StopRecordButton(onClick: () -> Unit) { + IconButton(onClick, Modifier.size(36.dp)) { + Icon( + painterResource(MR.images.ic_stop_filled), + stringResource(MR.strings.icon_descr_record_voice_message), + tint = MaterialTheme.colors.primary, + modifier = Modifier + .size(36.dp) + .padding(4.dp) + ) + } +} + +@Composable +private fun RecordVoiceButton(interactionSource: MutableInteractionSource) { + IconButton({}, Modifier.size(36.dp), interactionSource = interactionSource) { + Icon( + painterResource(MR.images.ic_keyboard_voice_filled), + stringResource(MR.strings.icon_descr_record_voice_message), + tint = MaterialTheme.colors.primary, + modifier = Modifier + .size(34.dp) + .padding(4.dp) + ) + } +} + +@Composable +private fun ProgressIndicator() { + CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 3.dp) +} + +@Composable +private fun CancelLiveMessageButton( + onClick: () -> Unit +) { + IconButton(onClick, Modifier.size(36.dp)) { + Icon( + painterResource(MR.images.ic_close), + stringResource(MR.strings.icon_descr_cancel_live_message), + tint = MaterialTheme.colors.primary, + modifier = Modifier + .size(36.dp) + .padding(4.dp) + ) + } +} + +@Composable +private fun SendMsgButton( + icon: Painter, + sizeDp: Animatable, + alpha: Animatable, + enabled: Boolean, + sendMessage: (Int?) -> Unit, + onLongClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + Box( + modifier = Modifier.requiredSize(36.dp) + .combinedClickable( + onClick = { sendMessage(null) }, + onLongClick = onLongClick, + enabled = enabled, + role = Role.Button, + interactionSource = interactionSource, + indication = rememberRipple(bounded = false, radius = 24.dp) + ), + contentAlignment = Alignment.Center + ) { + Icon( + icon, + stringResource(MR.strings.icon_descr_send_message), + tint = Color.White, + modifier = Modifier + .size(sizeDp.value.dp) + .padding(4.dp) + .alpha(alpha.value) + .clip(CircleShape) + .background(if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) + .padding(3.dp) + ) + } +} + +@Composable +private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { + val interactionSource = remember { MutableInteractionSource() } + Box( + modifier = Modifier.requiredSize(36.dp) + .clickable( + onClick = onClick, + enabled = enabled, + role = Role.Button, + interactionSource = interactionSource, + indication = rememberRipple(bounded = false, radius = 24.dp) + ), + contentAlignment = Alignment.Center + ) { + Icon( + BoltFilled, + stringResource(MR.strings.icon_descr_send_message), + tint = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + modifier = Modifier + .size(36.dp) + .padding(4.dp) + ) + } +} + +private fun startLiveMessage( + scope: CoroutineScope, + send: suspend () -> Unit, + update: suspend () -> Unit, + sendButtonSize: Animatable, + sendButtonAlpha: Animatable, + composeState: MutableState, + liveMessageAlertShown: SharedPreference +) { + fun run() { + scope.launch { + while (composeState.value.liveMessage != null) { + sendButtonSize.animateTo(if (sendButtonSize.value == 36f) 32f else 36f, tween(700, 50)) + } + sendButtonSize.snapTo(36f) + } + scope.launch { + while (composeState.value.liveMessage != null) { + sendButtonAlpha.animateTo(if (sendButtonAlpha.value == 1f) 0.75f else 1f, tween(700, 50)) + } + sendButtonAlpha.snapTo(1f) + } + scope.launch { + delay(3000) + while (composeState.value.liveMessage != null) { + update() + delay(3000) + } + } + } + + fun start() = withBGApi { + if (composeState.value.liveMessage == null) { + send() + } + run() + } + + if (liveMessageAlertShown.state.value) { + start() + } else { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.live_message), + text = generalGetString(MR.strings.send_live_message_desc), + confirmText = generalGetString(MR.strings.send_verb), + onConfirm = { + liveMessageAlertShown.set(true) + start() + }) + } +} + +private fun showNeedToAllowVoiceAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.allow_voice_messages_question), + text = generalGetString(MR.strings.you_need_to_allow_to_send_voice), + confirmText = generalGetString(MR.strings.allow_verb), + dismissText = generalGetString(MR.strings.cancel_verb), + onConfirm = onConfirm, + ) +} + +private fun showDisabledVoiceAlert(isDirectChat: Boolean) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.voice_messages_prohibited), + text = generalGetString( + if (isDirectChat) + MR.strings.ask_your_contact_to_enable_voice + else + MR.strings.only_group_owners_can_enable_voice + ) + ) +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewSendMsgView() { + val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) + val textStyle = remember { mutableStateOf(smallFont) } + SimpleXTheme { + SendMsgView( + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, + showVoiceRecordIcon = false, + recState = remember { mutableStateOf(RecordingState.NotStarted) }, + isDirectChat = true, + liveMessageAlertShown = SharedPreference(get = { true }, set = { }), + needToAllowVoiceToContact = false, + allowedVoiceByPrefs = true, + userIsObserver = false, + userCanSend = true, + allowVoiceToContact = {}, + timedMessageAllowed = false, + sendMessage = {}, + onMessageChange = { _ -> }, + textStyle = textStyle + ) + } +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewSendMsgViewEditing() { + val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) + val textStyle = remember { mutableStateOf(smallFont) } + val composeStateEditing = ComposeState(editingItem = ChatItem.getSampleData(), useLinkPreviews = true) + SimpleXTheme { + SendMsgView( + composeState = remember { mutableStateOf(composeStateEditing) }, + showVoiceRecordIcon = false, + recState = remember { mutableStateOf(RecordingState.NotStarted) }, + isDirectChat = true, + liveMessageAlertShown = SharedPreference(get = { true }, set = { }), + needToAllowVoiceToContact = false, + allowedVoiceByPrefs = true, + userIsObserver = false, + userCanSend = true, + allowVoiceToContact = {}, + timedMessageAllowed = false, + sendMessage = {}, + onMessageChange = { _ -> }, + textStyle = textStyle + ) + } +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewSendMsgViewInProgress() { + val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) + val textStyle = remember { mutableStateOf(smallFont) } + val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt", getAppFileUri("test.txt")), inProgress = true, useLinkPreviews = true) + SimpleXTheme { + SendMsgView( + composeState = remember { mutableStateOf(composeStateInProgress) }, + showVoiceRecordIcon = false, + recState = remember { mutableStateOf(RecordingState.NotStarted) }, + isDirectChat = true, + liveMessageAlertShown = SharedPreference(get = { true }, set = { }), + needToAllowVoiceToContact = false, + allowedVoiceByPrefs = true, + userIsObserver = false, + userCanSend = true, + allowVoiceToContact = {}, + timedMessageAllowed = false, + sendMessage = {}, + onMessageChange = { _ -> }, + textStyle = textStyle + ) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index 4c2bf1ca27..eac0bc3b85 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -1,814 +1,17 @@ package chat.simplex.app.views.chat import android.Manifest -import android.annotation.SuppressLint -import android.app.Activity -import android.content.Context -import android.content.pm.ActivityInfo -import android.content.res.Configuration -import android.os.Build -import android.text.InputType -import android.util.Log -import android.view.ViewGroup -import android.view.WindowManager -import android.view.inputmethod.* -import android.widget.EditText -import android.widget.TextView -import androidx.compose.animation.core.* -import androidx.compose.foundation.* -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.* -import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.* -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.* -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.* -import androidx.compose.ui.res.* -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.* -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.* -import androidx.compose.ui.viewinterop.AndroidView -import androidx.compose.ui.window.Dialog -import androidx.core.graphics.drawable.DrawableCompat -import androidx.core.view.inputmethod.EditorInfoCompat -import androidx.core.view.inputmethod.InputConnectionCompat -import androidx.core.widget.* -import chat.simplex.app.R -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.ItemAction -import chat.simplex.app.views.helpers.* +import androidx.compose.runtime.Composable import com.google.accompanist.permissions.rememberMultiplePermissionsState -import chat.simplex.res.MR -import dev.icerock.moko.resources.StringResource -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.* -import java.lang.reflect.Field @Composable -fun SendMsgView( - composeState: MutableState, - showVoiceRecordIcon: Boolean, - recState: MutableState, - isDirectChat: Boolean, - liveMessageAlertShown: SharedPreference, - needToAllowVoiceToContact: Boolean, - allowedVoiceByPrefs: Boolean, - userIsObserver: Boolean, - userCanSend: Boolean, - allowVoiceToContact: () -> Unit, - timedMessageAllowed: Boolean = false, - customDisappearingMessageTimePref: SharedPreference? = null, - sendMessage: (Int?) -> Unit, - sendLiveMessage: (suspend () -> Unit)? = null, - updateLiveMessage: (suspend () -> Unit)? = null, - cancelLiveMessage: (() -> Unit)? = null, - onMessageChange: (String) -> Unit, - textStyle: MutableState -) { - val showCustomDisappearingMessageDialog = remember { mutableStateOf(false) } - - if (showCustomDisappearingMessageDialog.value) { - CustomDisappearingMessageDialog( - sendMessage = sendMessage, - setShowDialog = { showCustomDisappearingMessageDialog.value = it }, - customDisappearingMessageTimePref = customDisappearingMessageTimePref - ) - } - - Box(Modifier.padding(vertical = 8.dp)) { - val cs = composeState.value - val showProgress = cs.inProgress && (cs.preview is ComposePreview.MediaPreview || cs.preview is ComposePreview.FilePreview) - val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing && - cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started) - val showDeleteTextButton = rememberSaveable { mutableStateOf(false) } - NativeKeyboard(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange) - // Disable clicks on text field - if (cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) { - Box( - Modifier - .matchParentSize() - .clickable(enabled = !userCanSend, indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.observer_cant_send_message_title), - text = generalGetString(MR.strings.observer_cant_send_message_desc) - ) - }) - ) - } - if (showDeleteTextButton.value) { - DeleteTextButton(composeState) - } - Box(Modifier.align(Alignment.BottomEnd)) { - val sendButtonSize = remember { Animatable(36f) } - val sendButtonAlpha = remember { Animatable(1f) } - val permissionsState = rememberMultiplePermissionsState(listOf(Manifest.permission.RECORD_AUDIO)) - val scope = rememberCoroutineScope() - LaunchedEffect(Unit) { - // Making LiveMessage alive when screen orientation was changed - if (cs.liveMessage != null && sendLiveMessage != null && updateLiveMessage != null) { - startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) - } - } - when { - showProgress -> ProgressIndicator() - showVoiceButton -> { - Row(verticalAlignment = Alignment.CenterVertically) { - val stopRecOnNextClick = remember { mutableStateOf(false) } - when { - needToAllowVoiceToContact || !allowedVoiceByPrefs || !userCanSend -> { - DisallowedVoiceButton(userCanSend) { - if (needToAllowVoiceToContact) { - showNeedToAllowVoiceAlert(allowVoiceToContact) - } else if (!allowedVoiceByPrefs) { - showDisabledVoiceAlert(isDirectChat) - } - } - } - !permissionsState.allPermissionsGranted -> - VoiceButtonWithoutPermission { permissionsState.launchMultiplePermissionRequest() } - else -> - RecordVoiceView(recState, stopRecOnNextClick) - } - if (sendLiveMessage != null - && updateLiveMessage != null - && (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value) - && cs.contextItem is ComposeContextItem.NoContextItem - ) { - Spacer(Modifier.width(10.dp)) - StartLiveMessageButton(userCanSend) { - if (composeState.value.preview is ComposePreview.NoPreview) { - startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) - } - } - } - } - } - cs.liveMessage?.sent == false && cs.message.isEmpty() -> { - CancelLiveMessageButton { - cancelLiveMessage?.invoke() - } - } - else -> { - val cs = composeState.value - val icon = if (cs.editing || cs.liveMessage != null) painterResource(MR.images.ic_check_filled) else painterResource(MR.images.ic_arrow_upward) - val disabled = !cs.sendEnabled() || - (!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) || - cs.endLiveDisabled - val showDropdown = rememberSaveable { mutableStateOf(false) } - - @Composable - fun MenuItems(): List<@Composable () -> Unit> { - val menuItems = mutableListOf<@Composable () -> Unit>() - - if (cs.liveMessage == null && !cs.editing) { - if ( - cs.preview !is ComposePreview.VoicePreview && - cs.contextItem is ComposeContextItem.NoContextItem && - sendLiveMessage != null && updateLiveMessage != null - ) { - menuItems.add { - ItemAction( - generalGetString(MR.strings.send_live_message), - BoltFilled, - onClick = { - startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) - showDropdown.value = false - } - ) - } - } - if (timedMessageAllowed) { - menuItems.add { - ItemAction( - generalGetString(MR.strings.disappearing_message), - painterResource(MR.images.ic_timer), - onClick = { - showCustomDisappearingMessageDialog.value = true - showDropdown.value = false - } - ) - } - } - } - - return menuItems - } - - val menuItems = MenuItems() - if (menuItems.isNotEmpty()) { - SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage) { showDropdown.value = true } - DefaultDropdownMenu(showDropdown) { - menuItems.forEach { composable -> composable() } - } - } else { - SendMsgButton(icon, sendButtonSize, sendButtonAlpha, !disabled, sendMessage) - } - } - } - } - } +fun allowedToRecordVoiceByPlatform(): Boolean { + val permissionsState = rememberMultiplePermissionsState(listOf(Manifest.permission.RECORD_AUDIO)) + return permissionsState.allPermissionsGranted } @Composable -private fun CustomDisappearingMessageDialog( - sendMessage: (Int?) -> Unit, - setShowDialog: (Boolean) -> Unit, - customDisappearingMessageTimePref: SharedPreference? -) { - val showCustomTimePicker = remember { mutableStateOf(false) } - - if (showCustomTimePicker.value) { - val selectedDisappearingMessageTime = remember { - mutableStateOf(customDisappearingMessageTimePref?.get?.invoke() ?: 300) - } - CustomTimePickerDialog( - selectedDisappearingMessageTime, - title = generalGetString(MR.strings.delete_after), - confirmButtonText = generalGetString(MR.strings.send_disappearing_message_send), - confirmButtonAction = { ttl -> - sendMessage(ttl) - customDisappearingMessageTimePref?.set?.invoke(ttl) - setShowDialog(false) - }, - cancel = { setShowDialog(false) } - ) - } else { - @Composable - fun ChoiceButton( - text: String, - onClick: () -> Unit - ) { - TextButton(onClick) { - Text( - text, - fontSize = 18.sp, - color = MaterialTheme.colors.primary - ) - } - } - - Dialog(onDismissRequest = { setShowDialog(false) }) { - Surface( - shape = RoundedCornerShape(corner = CornerSize(25.dp)) - ) { - Box( - contentAlignment = Alignment.Center - ) { - Column( - modifier = Modifier.padding(DEFAULT_PADDING), - verticalArrangement = Arrangement.spacedBy(6.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text(" ") // centers title - Text( - generalGetString(MR.strings.send_disappearing_message), - fontSize = 16.sp, - color = MaterialTheme.colors.secondary - ) - Icon( - painterResource(MR.images.ic_close), - generalGetString(MR.strings.icon_descr_close_button), - tint = MaterialTheme.colors.secondary, - modifier = Modifier - .size(25.dp) - .clickable { setShowDialog(false) } - ) - } - - ChoiceButton(generalGetString(MR.strings.send_disappearing_message_30_seconds)) { - sendMessage(30) - setShowDialog(false) - } - ChoiceButton(generalGetString(MR.strings.send_disappearing_message_1_minute)) { - sendMessage(60) - setShowDialog(false) - } - ChoiceButton(generalGetString(MR.strings.send_disappearing_message_5_minutes)) { - sendMessage(300) - setShowDialog(false) - } - ChoiceButton(generalGetString(MR.strings.send_disappearing_message_custom_time)) { - showCustomTimePicker.value = true - } - } - } - } - } - } -} - -@Composable -private fun NativeKeyboard( - composeState: MutableState, - textStyle: MutableState, - showDeleteTextButton: MutableState, - userIsObserver: Boolean, - onMessageChange: (String) -> Unit -) { - val cs = composeState.value - val textColor = MaterialTheme.colors.onBackground - val tintColor = MaterialTheme.colors.secondaryVariant - val padding = PaddingValues(12.dp, 7.dp, 45.dp, 0.dp) - val paddingStart = with(LocalDensity.current) { 12.dp.roundToPx() } - val paddingTop = with(LocalDensity.current) { 7.dp.roundToPx() } - val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() } - val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() } - var showKeyboard by remember { mutableStateOf(false) } - LaunchedEffect(cs.contextItem) { - if (cs.contextItem is ComposeContextItem.QuotedItem) { - delay(100) - showKeyboard = true - } else if (cs.contextItem is ComposeContextItem.EditingItem) { - // Keyboard will not show up if we try to show it too fast - delay(300) - showKeyboard = true - } - } - - AndroidView(modifier = Modifier, factory = { - val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) { - override fun setOnReceiveContentListener( - mimeTypes: Array?, - listener: android.view.OnReceiveContentListener? - ) { - super.setOnReceiveContentListener(mimeTypes, listener) - } - - override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { - val connection = super.onCreateInputConnection(editorInfo) - EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) - val onCommit = InputConnectionCompat.OnCommitContentListener { inputContentInfo, _, _ -> - try { - inputContentInfo.requestPermission() - } catch (e: Exception) { - return@OnCommitContentListener false - } - SimplexApp.context.chatModel.sharedContent.value = SharedContent.Media("", listOf(inputContentInfo.contentUri)) - true - } - return InputConnectionCompat.createWrapper(connection, editorInfo, onCommit) - } - } - editText.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - editText.maxLines = 16 - editText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or editText.inputType - editText.setTextColor(textColor.toArgb()) - editText.textSize = textStyle.value.fontSize.value - val drawable = it.getDrawable(R.drawable.send_msg_view_background)!! - DrawableCompat.setTint(drawable, tintColor.toArgb()) - editText.background = drawable - editText.setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom) - editText.setText(cs.message) - if (Build.VERSION.SDK_INT >= 29) { - editText.textCursorDrawable?.let { DrawableCompat.setTint(it, CurrentColors.value.colors.secondary.toArgb()) } - } else { - try { - val f: Field = TextView::class.java.getDeclaredField("mCursorDrawableRes") - f.isAccessible = true - f.set(editText, R.drawable.edit_text_cursor) - } catch (e: Exception) { - Log.e(chat.simplex.app.TAG, e.stackTraceToString()) - } - } - editText.doOnTextChanged { text, _, _, _ -> - if (!composeState.value.inProgress) { - onMessageChange(text.toString()) - } else if (text.toString() != composeState.value.message) { - editText.setText(composeState.value.message) - } - } - editText.doAfterTextChanged { text -> if (composeState.value.preview is ComposePreview.VoicePreview && text.toString() != "") editText.setText("") } - editText - }) { - it.setTextColor(textColor.toArgb()) - it.textSize = textStyle.value.fontSize.value - DrawableCompat.setTint(it.background, tintColor.toArgb()) - it.isFocusable = composeState.value.preview !is ComposePreview.VoicePreview - it.isFocusableInTouchMode = it.isFocusable - if (cs.message != it.text.toString()) { - it.setText(cs.message) - // Set cursor to the end of the text - it.setSelection(it.text.length) - } - if (showKeyboard) { - it.requestFocus() - val imm: InputMethodManager = SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT) - showKeyboard = false - } - showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress - } - if (composeState.value.preview is ComposePreview.VoicePreview) { - ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding) - } else if (userIsObserver) { - ComposeOverlay(MR.strings.you_are_observer, textStyle, padding) - } -} - -@Composable -private fun ComposeOverlay(textId: StringResource, textStyle: MutableState, padding: PaddingValues) { - Text( - generalGetString(textId), - Modifier.padding(padding), - color = MaterialTheme.colors.secondary, - style = textStyle.value.copy(fontStyle = FontStyle.Italic) - ) -} - -@Composable -private fun BoxScope.DeleteTextButton(composeState: MutableState) { - IconButton( - { composeState.value = composeState.value.copy(message = "") }, - Modifier.align(Alignment.TopEnd).size(36.dp) - ) { - Icon(painterResource(MR.images.ic_close), null, Modifier.padding(7.dp).size(36.dp), tint = MaterialTheme.colors.secondary) - } -} - -@Composable -private fun RecordVoiceView(recState: MutableState, stopRecOnNextClick: MutableState) { - val rec: Recorder = remember { RecorderNative() } - DisposableEffect(Unit) { onDispose { rec.stop() } } - val stopRecordingAndAddAudio: () -> Unit = { - recState.value.filePathNullable?.let { - recState.value = RecordingState.Finished(it, rec.stop()) - } - } - if (stopRecOnNextClick.value) { - LaunchedEffect(recState.value) { - if (recState.value is RecordingState.NotStarted) { - stopRecOnNextClick.value = false - } - } - // Lock orientation to current orientation because screen rotation will break the recording - LockToCurrentOrientationUntilDispose() - StopRecordButton(stopRecordingAndAddAudio) - } else { - val startRecording: () -> Unit = { - recState.value = RecordingState.Started( - filePath = rec.start { progress: Int?, finished: Boolean -> - val state = recState.value - if (state is RecordingState.Started && progress != null) { - recState.value = if (!finished) - RecordingState.Started(state.filePath, progress) - else - RecordingState.Finished(state.filePath, progress) - } - }, - ) - } - val interactionSource = interactionSourceWithTapDetection( - onPress = { if (recState.value is RecordingState.NotStarted) startRecording() }, - onClick = { - if (stopRecOnNextClick.value) { - stopRecordingAndAddAudio() - } else { - // tapped and didn't hold a finger - stopRecOnNextClick.value = true - } - }, - onCancel = stopRecordingAndAddAudio, - onRelease = stopRecordingAndAddAudio - ) - RecordVoiceButton(interactionSource) - } -} - -@Composable -private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) { - IconButton(onClick, Modifier.size(36.dp), enabled = enabled) { - Icon( - painterResource(MR.images.ic_keyboard_voice), - stringResource(MR.strings.icon_descr_record_voice_message), - tint = MaterialTheme.colors.secondary, - modifier = Modifier - .size(36.dp) - .padding(4.dp) - ) - } -} - -@Composable -private fun VoiceButtonWithoutPermission(onClick: () -> Unit) { - IconButton(onClick, Modifier.size(36.dp)) { - Icon( - painterResource(MR.images.ic_keyboard_voice_filled), - stringResource(MR.strings.icon_descr_record_voice_message), - tint = MaterialTheme.colors.primary, - modifier = Modifier - .size(34.dp) - .padding(4.dp) - ) - } -} - -@Composable -private fun LockToCurrentOrientationUntilDispose() { - val context = LocalContext.current - DisposableEffect(Unit) { - val activity = context as Activity - val manager = context.getSystemService(Activity.WINDOW_SERVICE) as WindowManager - val rotation = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) manager.defaultDisplay.rotation else activity.display?.rotation - activity.requestedOrientation = when (rotation) { - android.view.Surface.ROTATION_90 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - android.view.Surface.ROTATION_180 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT - android.view.Surface.ROTATION_270 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - else -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - // Unlock orientation - onDispose { activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } - } -} - -@Composable -private fun StopRecordButton(onClick: () -> Unit) { - IconButton(onClick, Modifier.size(36.dp)) { - Icon( - painterResource(MR.images.ic_stop_filled), - stringResource(MR.strings.icon_descr_record_voice_message), - tint = MaterialTheme.colors.primary, - modifier = Modifier - .size(36.dp) - .padding(4.dp) - ) - } -} - -@Composable -private fun RecordVoiceButton(interactionSource: MutableInteractionSource) { - IconButton({}, Modifier.size(36.dp), interactionSource = interactionSource) { - Icon( - painterResource(MR.images.ic_keyboard_voice_filled), - stringResource(MR.strings.icon_descr_record_voice_message), - tint = MaterialTheme.colors.primary, - modifier = Modifier - .size(34.dp) - .padding(4.dp) - ) - } -} - -@Composable -private fun ProgressIndicator() { - CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 3.dp) -} - -@Composable -private fun CancelLiveMessageButton( - onClick: () -> Unit -) { - IconButton(onClick, Modifier.size(36.dp)) { - Icon( - painterResource(MR.images.ic_close), - stringResource(MR.strings.icon_descr_cancel_live_message), - tint = MaterialTheme.colors.primary, - modifier = Modifier - .size(36.dp) - .padding(4.dp) - ) - } -} - -@Composable -private fun SendMsgButton( - icon: Painter, - sizeDp: Animatable, - alpha: Animatable, - enabled: Boolean, - sendMessage: (Int?) -> Unit, - onLongClick: (() -> Unit)? = null -) { - val interactionSource = remember { MutableInteractionSource() } - Box( - modifier = Modifier.requiredSize(36.dp) - .combinedClickable( - onClick = { sendMessage(null) }, - onLongClick = onLongClick, - enabled = enabled, - role = Role.Button, - interactionSource = interactionSource, - indication = rememberRipple(bounded = false, radius = 24.dp) - ), - contentAlignment = Alignment.Center - ) { - Icon( - icon, - stringResource(MR.strings.icon_descr_send_message), - tint = Color.White, - modifier = Modifier - .size(sizeDp.value.dp) - .padding(4.dp) - .alpha(alpha.value) - .clip(CircleShape) - .background(if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) - .padding(3.dp) - ) - } -} - -@Composable -private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { - val interactionSource = remember { MutableInteractionSource() } - Box( - modifier = Modifier.requiredSize(36.dp) - .clickable( - onClick = onClick, - enabled = enabled, - role = Role.Button, - interactionSource = interactionSource, - indication = rememberRipple(bounded = false, radius = 24.dp) - ), - contentAlignment = Alignment.Center - ) { - Icon( - BoltFilled, - stringResource(MR.strings.icon_descr_send_message), - tint = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - modifier = Modifier - .size(36.dp) - .padding(4.dp) - ) - } -} - -private fun startLiveMessage( - scope: CoroutineScope, - send: suspend () -> Unit, - update: suspend () -> Unit, - sendButtonSize: Animatable, - sendButtonAlpha: Animatable, - composeState: MutableState, - liveMessageAlertShown: SharedPreference -) { - fun run() { - scope.launch { - while (composeState.value.liveMessage != null) { - sendButtonSize.animateTo(if (sendButtonSize.value == 36f) 32f else 36f, tween(700, 50)) - } - sendButtonSize.snapTo(36f) - } - scope.launch { - while (composeState.value.liveMessage != null) { - sendButtonAlpha.animateTo(if (sendButtonAlpha.value == 1f) 0.75f else 1f, tween(700, 50)) - } - sendButtonAlpha.snapTo(1f) - } - scope.launch { - delay(3000) - while (composeState.value.liveMessage != null) { - update() - delay(3000) - } - } - } - - fun start() = withBGApi { - if (composeState.value.liveMessage == null) { - send() - } - run() - } - - if (liveMessageAlertShown.state.value) { - start() - } else { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.live_message), - text = generalGetString(MR.strings.send_live_message_desc), - confirmText = generalGetString(MR.strings.send_verb), - onConfirm = { - liveMessageAlertShown.set(true) - start() - }) - } -} - -private fun showNeedToAllowVoiceAlert(onConfirm: () -> Unit) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.allow_voice_messages_question), - text = generalGetString(MR.strings.you_need_to_allow_to_send_voice), - confirmText = generalGetString(MR.strings.allow_verb), - dismissText = generalGetString(MR.strings.cancel_verb), - onConfirm = onConfirm, - ) -} - -private fun showDisabledVoiceAlert(isDirectChat: Boolean) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.voice_messages_prohibited), - text = generalGetString( - if (isDirectChat) - MR.strings.ask_your_contact_to_enable_voice - else - MR.strings.only_group_owners_can_enable_voice - ) - ) -} - -@Preview(showBackground = true) -@Preview( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -) -@Composable -fun PreviewSendMsgView() { - val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) - val textStyle = remember { mutableStateOf(smallFont) } - SimpleXTheme { - SendMsgView( - composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, - showVoiceRecordIcon = false, - recState = remember { mutableStateOf(RecordingState.NotStarted) }, - isDirectChat = true, - liveMessageAlertShown = SharedPreference(get = { true }, set = { }), - needToAllowVoiceToContact = false, - allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, - allowVoiceToContact = {}, - timedMessageAllowed = false, - sendMessage = {}, - onMessageChange = { _ -> }, - textStyle = textStyle - ) - } -} - -@Preview(showBackground = true) -@Preview( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -) -@Composable -fun PreviewSendMsgViewEditing() { - val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) - val textStyle = remember { mutableStateOf(smallFont) } - val composeStateEditing = ComposeState(editingItem = ChatItem.getSampleData(), useLinkPreviews = true) - SimpleXTheme { - SendMsgView( - composeState = remember { mutableStateOf(composeStateEditing) }, - showVoiceRecordIcon = false, - recState = remember { mutableStateOf(RecordingState.NotStarted) }, - isDirectChat = true, - liveMessageAlertShown = SharedPreference(get = { true }, set = { }), - needToAllowVoiceToContact = false, - allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, - allowVoiceToContact = {}, - timedMessageAllowed = false, - sendMessage = {}, - onMessageChange = { _ -> }, - textStyle = textStyle - ) - } -} - -@Preview(showBackground = true) -@Preview( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -) -@Composable -fun PreviewSendMsgViewInProgress() { - val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) - val textStyle = remember { mutableStateOf(smallFont) } - val composeStateInProgress = ComposeState(preview = ComposePreview.FilePreview("test.txt", getAppFileUri("test.txt")), inProgress = true, useLinkPreviews = true) - SimpleXTheme { - SendMsgView( - composeState = remember { mutableStateOf(composeStateInProgress) }, - showVoiceRecordIcon = false, - recState = remember { mutableStateOf(RecordingState.NotStarted) }, - isDirectChat = true, - liveMessageAlertShown = SharedPreference(get = { true }, set = { }), - needToAllowVoiceToContact = false, - allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, - allowVoiceToContact = {}, - timedMessageAllowed = false, - sendMessage = {}, - onMessageChange = { _ -> }, - textStyle = textStyle - ) - } +fun VoiceButtonWithoutPermissionByPlatform() { + val permissionsState = rememberMultiplePermissionsState(listOf(Manifest.permission.RECORD_AUDIO)) + VoiceButtonWithoutPermission { permissionsState.launchMultiplePermissionRequest() } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt index 9a122f360b..390b839937 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt @@ -15,7 +15,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.QRCode diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt index 555d6c4a2e..8a60681031 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalView @@ -25,8 +24,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.hideKeyboard import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.ChatInfoToolbarTitle import chat.simplex.app.views.helpers.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt index 025251cd8b..ce1efdeafe 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt @@ -14,8 +14,8 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.QRCode diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt index 8c17466312..c1bc15dbd9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt @@ -24,8 +24,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.* import chat.simplex.app.views.helpers.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt index 5031babd49..1be6cca41e 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.cropToSquare +import chat.simplex.app.platform.resizeImageToStrSize import chat.simplex.app.ui.theme.* import chat.simplex.app.views.ProfileNameField import chat.simplex.app.views.helpers.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt index 7cd7c9ca38..2e04171ea2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt @@ -18,9 +18,8 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.copyText import chat.simplex.app.ui.theme.DEFAULT_PADDING import chat.simplex.app.views.chat.item.MarkdownText import chat.simplex.app.views.helpers.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt index 7c9e49766b..21e6346eca 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt @@ -1,6 +1,10 @@ package chat.simplex.app.views.chat.item +import android.net.Uri import android.widget.Toast +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CornerSize @@ -19,12 +23,15 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.tooling.preview.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R +import chat.simplex.app.SimplexApp import chat.simplex.app.model.* +import chat.simplex.app.platform.getLoadedFilePath import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.res.MR import kotlinx.datetime.Clock +import java.io.BufferedOutputStream +import java.io.File @Composable fun CIFileView( @@ -206,6 +213,30 @@ fun CIFileView( } } +@Composable +fun rememberSaveFileLauncher(ciFile: CIFile?): ManagedActivityResultLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.CreateDocument(), + onResult = { destination -> + destination?.let { + val cxt = SimplexApp.context + val filePath = getLoadedFilePath(ciFile) + if (filePath != null) { + val contentResolver = cxt.contentResolver + contentResolver.openOutputStream(destination)?.let { stream -> + val outputStream = BufferedOutputStream(stream) + File(filePath).inputStream().use { it.copyTo(outputStream) } + outputStream.close() + Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() + } + } else { + Toast.makeText(cxt, generalGetString(MR.strings.file_not_found), Toast.LENGTH_SHORT).show() + } + } + } + ) + + class ChatItemProvider: PreviewParameterProvider { private val sentFile = ChatItem( chatDir = CIDirection.DirectSnd(), diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt new file mode 100644 index 0000000000..35bc7ad55c --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt @@ -0,0 +1,188 @@ +package chat.simplex.app.views.chat.item + +import android.graphics.Bitmap +import androidx.compose.foundation.Image +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Icon +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.* +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.core.content.FileProvider +import chat.simplex.app.BuildConfig +import chat.simplex.app.model.* +import chat.simplex.app.platform.base64ToBitmap +import chat.simplex.app.platform.getLoadedFilePath +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import java.io.File + +@Composable +fun CIImageView( + image: String, + file: CIFile?, + imageProvider: () -> ImageGalleryProvider, + showMenu: MutableState, + receiveFile: (Long) -> Unit +) { + @Composable + fun progressIndicator() { + CircularProgressIndicator( + Modifier.size(16.dp), + color = Color.White, + strokeWidth = 2.dp + ) + } + + @Composable + fun fileIcon(icon: Painter, stringId: StringResource) { + Icon( + icon, + stringResource(stringId), + Modifier.fillMaxSize(), + tint = Color.White + ) + } + + @Composable + fun loadingIndicator() { + if (file != null) { + Box( + Modifier + .padding(8.dp) + .size(20.dp), + contentAlignment = Alignment.Center + ) { + when (file.fileStatus) { + is CIFileStatus.SndStored -> + when (file.fileProtocol) { + FileProtocol.XFTP -> progressIndicator() + FileProtocol.SMP -> {} + } + is CIFileStatus.SndTransfer -> progressIndicator() + is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_image_snd_complete) + is CIFileStatus.SndCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.SndError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_asked_to_receive) + is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_image) + is CIFileStatus.RcvTransfer -> progressIndicator() + is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + else -> {} + } + } + } + } + + @Composable + fun imageViewFullWidth(): Dp { + val approximatePadding = 100.dp + return with(LocalDensity.current) { minOf(1000.dp, LocalView.current.width.toDp() - approximatePadding) } + } + + @Composable + fun imageView(imageBitmap: Bitmap, onClick: () -> Unit) { + Image( + imageBitmap.asImageBitmap(), + contentDescription = stringResource(MR.strings.image_descr), + // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView + // if text is short and take all available width if text is long + modifier = Modifier + .width(if (imageBitmap.width * 0.97 <= imageBitmap.height) imageViewFullWidth() * 0.75f else 1000.dp) + .combinedClickable( + onLongClick = { showMenu.value = true }, + onClick = onClick + ), + contentScale = ContentScale.FillWidth, + ) + } + + @Composable + fun imageView(painter: Painter, onClick: () -> Unit) { + Image( + painter, + contentDescription = stringResource(MR.strings.image_descr), + // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView + // if text is short and take all available width if text is long + modifier = Modifier + .width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else 1000.dp) + .combinedClickable( + onLongClick = { showMenu.value = true }, + onClick = onClick + ), + contentScale = ContentScale.FillWidth, + ) + } + + fun fileSizeValid(): Boolean { + if (file != null) { + return file.fileSize <= getMaxFileSize(file.fileProtocol) + } + return false + } + + fun imageAndFilePath(file: CIFile?): Pair { + val imageBitmap: Bitmap? = getLoadedImage(file) + val filePath = getLoadedFilePath(file) + return imageBitmap to filePath + } + + Box( + Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), + contentAlignment = Alignment.TopEnd + ) { + val context = LocalContext.current + val (imageBitmap, filePath) = remember(file) { imageAndFilePath(file) } + if (imageBitmap != null && filePath != null) { + val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) } + SimpleAndAnimatedImageView(uri, imageBitmap.asImageBitmap(), file, imageProvider, @Composable { painter, onClick -> imageView(painter, onClick) }) + } else { + imageView(base64ToBitmap(image), onClick = { + if (file != null) { + when (file.fileStatus) { + CIFileStatus.RcvInvitation -> + if (fileSizeValid()) { + receiveFile(file.fileId) + } else { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.large_file), + String.format(generalGetString(MR.strings.contact_sent_large_file), formatBytes(getMaxFileSize(file.fileProtocol))) + ) + } + CIFileStatus.RcvAccepted -> + when (file.fileProtocol) { + FileProtocol.XFTP -> + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.waiting_for_image), + generalGetString(MR.strings.image_will_be_received_when_contact_completes_uploading) + ) + FileProtocol.SMP -> + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.waiting_for_image), + generalGetString(MR.strings.image_will_be_received_when_contact_is_online) + ) + } + CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ? + CIFileStatus.RcvComplete -> {} // ? + CIFileStatus.RcvCancelled -> {} // TODO + else -> {} + } + } + }) + } + loadingIndicator() + } +} + diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt index 642757f271..3c3337db88 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt @@ -1,213 +1,50 @@ package chat.simplex.app.views.chat.item -import android.graphics.Bitmap -import android.os.Build.VERSION.SDK_INT -import androidx.compose.foundation.Image -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Icon +import android.net.Uri +import android.os.Build import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.* -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.core.content.FileProvider import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.getLoadedFilePath +import chat.simplex.app.platform.hideKeyboard import chat.simplex.app.views.helpers.* import coil.ImageLoader import coil.compose.rememberAsyncImagePainter import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest -import chat.simplex.res.MR -import dev.icerock.moko.resources.StringResource -import java.io.File @Composable -fun CIImageView( - image: String, +fun SimpleAndAnimatedImageView( + uri: Uri, + imageBitmap: ImageBitmap, file: CIFile?, imageProvider: () -> ImageGalleryProvider, - showMenu: MutableState, - receiveFile: (Long) -> Unit + ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit ) { - @Composable - fun progressIndicator() { - CircularProgressIndicator( - Modifier.size(16.dp), - color = Color.White, - strokeWidth = 2.dp - ) - } - - @Composable - fun fileIcon(icon: Painter, stringId: StringResource) { - Icon( - icon, - stringResource(stringId), - Modifier.fillMaxSize(), - tint = Color.White - ) - } - - @Composable - fun loadingIndicator() { - if (file != null) { - Box( - Modifier - .padding(8.dp) - .size(20.dp), - contentAlignment = Alignment.Center - ) { - when (file.fileStatus) { - is CIFileStatus.SndStored -> - when (file.fileProtocol) { - FileProtocol.XFTP -> progressIndicator() - FileProtocol.SMP -> {} - } - is CIFileStatus.SndTransfer -> progressIndicator() - is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_image_snd_complete) - is CIFileStatus.SndCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - is CIFileStatus.SndError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_asked_to_receive) - is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_image) - is CIFileStatus.RcvTransfer -> progressIndicator() - is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - else -> {} - } + val imagePainter = rememberAsyncImagePainter( + ImageRequest.Builder(SimplexApp.context).data(data = uri).size(coil.size.Size.ORIGINAL).build(), + placeholder = BitmapPainter(imageBitmap), // show original image while it's still loading by coil + imageLoader = imageLoader + ) + val view = LocalView.current + ImageView(imagePainter) { + hideKeyboard(view) + if (getLoadedFilePath(file) != null) { + ModalManager.shared.showCustomModal(animated = false) { close -> + ImageFullScreenView(imageProvider, close) } } } - - @Composable - fun imageViewFullWidth(): Dp { - val approximatePadding = 100.dp - return with(LocalDensity.current) { minOf(1000.dp, LocalView.current.width.toDp() - approximatePadding) } - } - - @Composable - fun imageView(imageBitmap: Bitmap, onClick: () -> Unit) { - Image( - imageBitmap.asImageBitmap(), - contentDescription = stringResource(MR.strings.image_descr), - // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView - // if text is short and take all available width if text is long - modifier = Modifier - .width(if (imageBitmap.width * 0.97 <= imageBitmap.height) imageViewFullWidth() * 0.75f else 1000.dp) - .combinedClickable( - onLongClick = { showMenu.value = true }, - onClick = onClick - ), - contentScale = ContentScale.FillWidth, - ) - } - - @Composable - fun imageView(painter: Painter, onClick: () -> Unit) { - Image( - painter, - contentDescription = stringResource(MR.strings.image_descr), - // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView - // if text is short and take all available width if text is long - modifier = Modifier - .width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else 1000.dp) - .combinedClickable( - onLongClick = { showMenu.value = true }, - onClick = onClick - ), - contentScale = ContentScale.FillWidth, - ) - } - - fun fileSizeValid(): Boolean { - if (file != null) { - return file.fileSize <= getMaxFileSize(file.fileProtocol) - } - return false - } - - fun imageAndFilePath(file: CIFile?): Pair { - val imageBitmap: Bitmap? = getLoadedImage(file) - val filePath = getLoadedFilePath(file) - return imageBitmap to filePath - } - - Box( - Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), - contentAlignment = Alignment.TopEnd - ) { - val context = LocalContext.current - val (imageBitmap, filePath) = remember(file) { imageAndFilePath(file) } - if (imageBitmap != null && filePath != null) { - val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) } - val imagePainter = rememberAsyncImagePainter( - ImageRequest.Builder(context).data(data = uri).size(coil.size.Size.ORIGINAL).build(), - placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil - imageLoader = imageLoader - ) - val view = LocalView.current - imageView(imagePainter, onClick = { - hideKeyboard(view) - if (getLoadedFilePath(file) != null) { - ModalManager.shared.showCustomModal(animated = false) { close -> - ImageFullScreenView(imageProvider, close) - } - } - }) - } else { - imageView(base64ToBitmap(image), onClick = { - if (file != null) { - when (file.fileStatus) { - CIFileStatus.RcvInvitation -> - if (fileSizeValid()) { - receiveFile(file.fileId) - } else { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.large_file), - String.format(generalGetString(MR.strings.contact_sent_large_file), formatBytes(getMaxFileSize(file.fileProtocol))) - ) - } - CIFileStatus.RcvAccepted -> - when (file.fileProtocol) { - FileProtocol.XFTP -> - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.waiting_for_image), - generalGetString(MR.strings.image_will_be_received_when_contact_completes_uploading) - ) - FileProtocol.SMP -> - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.waiting_for_image), - generalGetString(MR.strings.image_will_be_received_when_contact_is_online) - ) - } - CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ? - CIFileStatus.RcvComplete -> {} // ? - CIFileStatus.RcvCancelled -> {} // TODO - else -> {} - } - } - }) - } - loadingIndicator() - } } private val imageLoader = ImageLoader.Builder(SimplexApp.context) .components { - if (SDK_INT >= 28) { + if (Build.VERSION.SDK_INT >= 28) { add(ImageDecoderDecoder.Factory()) } else { add(GifDecoder.Factory()) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIInvalidJSONView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIInvalidJSONView.kt index 64111ee4ea..dc0ef7f415 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIInvalidJSONView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIInvalidJSONView.kt @@ -11,7 +11,7 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp -import chat.simplex.app.R +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.DEFAULT_PADDING import chat.simplex.app.views.helpers.* import chat.simplex.app.views.usersettings.SettingsActionItem diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt new file mode 100644 index 0000000000..6a421c6348 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt @@ -0,0 +1,318 @@ +package chat.simplex.app.views.chat.item + +import android.graphics.Bitmap +import android.net.Uri +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.* +import androidx.compose.ui.unit.* +import androidx.core.content.FileProvider +import chat.simplex.app.BuildConfig +import chat.simplex.app.model.* +import chat.simplex.app.platform.* +import chat.simplex.app.ui.theme.DEFAULT_PADDING_HALF +import chat.simplex.app.ui.theme.WarningOrange +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import java.io.File + +@Composable +fun CIVideoView( + image: String, + duration: Int, + file: CIFile?, + imageProvider: () -> ImageGalleryProvider, + showMenu: MutableState, + receiveFile: (Long) -> Unit +) { + Box( + Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), + contentAlignment = Alignment.TopEnd + ) { + val context = LocalContext.current + val filePath = remember(file) { getLoadedFilePath(file) } + val preview = remember(image) { base64ToBitmap(image) } + if (file != null && filePath != null) { + val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) } + val view = LocalView.current + VideoView(uri, file, preview, duration * 1000L, showMenu, onClick = { + hideKeyboard(view) + ModalManager.shared.showCustomModal(animated = false) { close -> + ImageFullScreenView(imageProvider, close) + } + }) + } else { + Box { + ImageView(preview, showMenu, onClick = { + if (file != null) { + when (file.fileStatus) { + CIFileStatus.RcvInvitation -> + receiveFileIfValidSize(file, receiveFile) + CIFileStatus.RcvAccepted -> + when (file.fileProtocol) { + FileProtocol.XFTP -> + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.waiting_for_video), + generalGetString(MR.strings.video_will_be_received_when_contact_completes_uploading) + ) + + FileProtocol.SMP -> + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.waiting_for_video), + generalGetString(MR.strings.video_will_be_received_when_contact_is_online) + ) + } + CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ? + CIFileStatus.RcvComplete -> {} // ? + CIFileStatus.RcvCancelled -> {} // TODO + else -> {} + } + } + }) + if (file != null) { + DurationProgress(file, remember { mutableStateOf(false) }, remember { mutableStateOf(duration * 1000L) }, remember { mutableStateOf(0L) }/*, soundEnabled*/) + } + if (file?.fileStatus is CIFileStatus.RcvInvitation) { + PlayButton(error = false, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) } + } + } + } + loadingIndicator(file) + } +} + +@Composable +private fun VideoView(uri: Uri, file: CIFile, defaultPreview: Bitmap, defaultDuration: Long, showMenu: MutableState, onClick: () -> Unit) { + val context = LocalContext.current + val player = remember(uri) { VideoPlayer.getOrCreate(uri, false, defaultPreview, defaultDuration, true) } + val videoPlaying = remember(uri.path) { player.videoPlaying } + val progress = remember(uri.path) { player.progress } + val duration = remember(uri.path) { player.duration } + val preview by remember { player.preview } + // val soundEnabled by rememberSaveable(uri.path) { player.soundEnabled } + val brokenVideo by rememberSaveable(uri.path) { player.brokenVideo } + val play = { + player.enableSound(true) + player.play(true) + } + val stop = { + player.enableSound(false) + player.stop() + } + val showPreview = remember { derivedStateOf { !videoPlaying.value || progress.value == 0L } } + DisposableEffect(Unit) { + onDispose { + stop() + } + } + Box { + val windowWidth = LocalWindowWidth() + val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp } + PlayerView( + player, + width, + onClick = onClick, + onLongClick = { showMenu.value = true }, + stop + ) + if (showPreview.value) { + ImageView(preview, showMenu, onClick) + PlayButton(brokenVideo, onLongClick = { showMenu.value = true }, play) + } + DurationProgress(file, videoPlaying, duration, progress/*, soundEnabled*/) + } +} + +@Composable +private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, onClick: () -> Unit) { + Surface( + Modifier.align(Alignment.Center), + color = Color.Black.copy(alpha = 0.25f), + shape = RoundedCornerShape(percent = 50) + ) { + Box( + Modifier + .defaultMinSize(minWidth = 40.dp, minHeight = 40.dp) + .combinedClickable(onClick = onClick, onLongClick = onLongClick), + contentAlignment = Alignment.Center + ) { + Icon( + painterResource(MR.images.ic_play_arrow_filled), + contentDescription = null, + tint = if (error) WarningOrange else Color.White + ) + } + } +} + +@Composable +private fun DurationProgress(file: CIFile, playing: MutableState, duration: MutableState, progress: MutableState/*, soundEnabled: MutableState*/) { + if (duration.value > 0L || progress.value > 0) { + Row { + Box( + Modifier + .padding(DEFAULT_PADDING_HALF) + .background(Color.Black.copy(alpha = 0.35f), RoundedCornerShape(percent = 50)) + .padding(vertical = 2.dp, horizontal = 4.dp) + ) { + val time = if (progress.value > 0) progress.value else duration.value + val timeStr = durationText((time / 1000).toInt()) + val width = if (timeStr.length <= 5) 44 else 50 + Text( + timeStr, + Modifier.widthIn(min = with(LocalDensity.current) { width.sp.toDp() }).padding(horizontal = 4.dp), + fontSize = 13.sp, + color = Color.White + ) + /*if (!soundEnabled.value) { + Icon(painterResource(MR.images.ic_volume_off_filled), null, + Modifier.padding(start = 5.dp).size(10.dp), + tint = Color.White + ) + }*/ + } + if (!playing.value) { + Box( + Modifier + .padding(top = DEFAULT_PADDING_HALF) + .background(Color.Black.copy(alpha = 0.35f), RoundedCornerShape(percent = 50)) + .padding(vertical = 2.dp, horizontal = 4.dp) + ) { + Text( + formatBytes(file.fileSize), + Modifier.padding(horizontal = 4.dp), + fontSize = 13.sp, + color = Color.White + ) + } + } + } + } +} + +@Composable +private fun ImageView(preview: Bitmap, showMenu: MutableState, onClick: () -> Unit) { + val windowWidth = LocalWindowWidth() + val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp } + Image( + preview.asImageBitmap(), + contentDescription = stringResource(MR.strings.video_descr), + modifier = Modifier + .width(width) + .combinedClickable( + onLongClick = { showMenu.value = true }, + onClick = onClick + ), + contentScale = ContentScale.FillWidth, + ) +} + +@Composable +private fun progressIndicator() { + CircularProgressIndicator( + Modifier.size(16.dp), + color = Color.White, + strokeWidth = 2.dp + ) +} + +@Composable +private fun fileIcon(icon: Painter, stringId: StringResource) { + Icon( + icon, + stringResource(stringId), + Modifier.fillMaxSize(), + tint = Color.White + ) +} + +@Composable +private fun progressCircle(progress: Long, total: Long) { + val angle = 360f * (progress.toDouble() / total.toDouble()).toFloat() + val strokeWidth = with(LocalDensity.current) { 2.dp.toPx() } + val strokeColor = Color.White + Surface( + Modifier.drawRingModifier(angle, strokeColor, strokeWidth), + color = Color.Transparent, + shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)) + ) { + Box(Modifier.size(16.dp)) + } +} + +@Composable +private fun loadingIndicator(file: CIFile?) { + if (file != null) { + Box( + Modifier + .padding(8.dp) + .size(20.dp), + contentAlignment = Alignment.Center + ) { + when (file.fileStatus) { + is CIFileStatus.SndStored -> + when (file.fileProtocol) { + FileProtocol.XFTP -> progressIndicator() + FileProtocol.SMP -> {} + } + is CIFileStatus.SndTransfer -> + when (file.fileProtocol) { + FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal) + FileProtocol.SMP -> progressIndicator() + } + is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_video_snd_complete) + is CIFileStatus.SndCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.SndError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_video_asked_to_receive) + is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_video) + is CIFileStatus.RcvTransfer -> + if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) { + progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal) + } else { + progressIndicator() + } + is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + else -> {} + } + } + } +} + +private fun fileSizeValid(file: CIFile?): Boolean { + if (file != null) { + return file.fileSize <= getMaxFileSize(file.fileProtocol) + } + return false +} + +private fun receiveFileIfValidSize(file: CIFile, receiveFile: (Long) -> Unit) { + if (fileSizeValid(file)) { + receiveFile(file.fileId) + } else { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.large_file), + String.format(generalGetString(MR.strings.contact_sent_large_file), formatBytes(getMaxFileSize(file.fileProtocol))) + ) + } +} + +private fun videoViewFullWidth(windowWidth: Dp): Dp { + val approximatePadding = 100.dp + return minOf(1000.dp, windowWidth - approximatePadding) +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt index f6123e7bed..585c1eb229 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt @@ -1,239 +1,38 @@ package chat.simplex.app.views.chat.item -import android.graphics.Bitmap import android.graphics.Rect -import android.net.Uri import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.* import androidx.compose.ui.platform.* -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.* import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.content.FileProvider -import androidx.core.graphics.drawable.toDrawable -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH import com.google.android.exoplayer2.ui.StyledPlayerView -import chat.simplex.res.MR -import dev.icerock.moko.resources.StringResource -import java.io.File + @Composable -fun CIVideoView( - image: String, - duration: Int, - file: CIFile?, - imageProvider: () -> ImageGalleryProvider, - showMenu: MutableState, - receiveFile: (Long) -> Unit -) { - Box( - Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), - contentAlignment = Alignment.TopEnd - ) { - val context = LocalContext.current - val filePath = remember(file) { getLoadedFilePath(file) } - val preview = remember(image) { base64ToBitmap(image) } - if (file != null && filePath != null) { - val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) } - val view = LocalView.current - VideoView(uri, file, preview, duration * 1000L, showMenu, onClick = { - hideKeyboard(view) - ModalManager.shared.showCustomModal(animated = false) { close -> - ImageFullScreenView(imageProvider, close) - } - }) - } else { - Box { - ImageView(preview, showMenu, onClick = { - if (file != null) { - when (file.fileStatus) { - CIFileStatus.RcvInvitation -> - receiveFileIfValidSize(file, receiveFile) - CIFileStatus.RcvAccepted -> - when (file.fileProtocol) { - FileProtocol.XFTP -> - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.waiting_for_video), - generalGetString(MR.strings.video_will_be_received_when_contact_completes_uploading) - ) - - FileProtocol.SMP -> - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.waiting_for_video), - generalGetString(MR.strings.video_will_be_received_when_contact_is_online) - ) - } - CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ? - CIFileStatus.RcvComplete -> {} // ? - CIFileStatus.RcvCancelled -> {} // TODO - else -> {} - } - } - }) - if (file != null) { - DurationProgress(file, remember { mutableStateOf(false) }, remember { mutableStateOf(duration * 1000L) }, remember { mutableStateOf(0L) }/*, soundEnabled*/) - } - if (file?.fileStatus is CIFileStatus.RcvInvitation) { - PlayButton(error = false, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) } - } +fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: () -> Unit, stop: () -> Unit) { + AndroidView( + factory = { ctx -> + StyledPlayerView(ctx).apply { + useController = false + resizeMode = RESIZE_MODE_FIXED_WIDTH + this.player = player.player } - } - loadingIndicator(file) - } -} - -@Composable -private fun VideoView(uri: Uri, file: CIFile, defaultPreview: Bitmap, defaultDuration: Long, showMenu: MutableState, onClick: () -> Unit) { - val context = LocalContext.current - val player = remember(uri) { VideoPlayer.getOrCreate(uri, false, defaultPreview, defaultDuration, true) } - val videoPlaying = remember(uri.path) { player.videoPlaying } - val progress = remember(uri.path) { player.progress } - val duration = remember(uri.path) { player.duration } - val preview by remember { player.preview } -// val soundEnabled by rememberSaveable(uri.path) { player.soundEnabled } - val brokenVideo by rememberSaveable(uri.path) { player.brokenVideo } - val play = { - player.enableSound(true) - player.play(true) - } - val stop = { - player.enableSound(false) - player.stop() - } - val showPreview = remember { derivedStateOf { !videoPlaying.value || progress.value == 0L } } - DisposableEffect(Unit) { - onDispose { - stop() - } - } - Box { - val windowWidth = LocalWindowWidth() - val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp } - AndroidView( - factory = { ctx -> - StyledPlayerView(ctx).apply { - useController = false - resizeMode = RESIZE_MODE_FIXED_WIDTH - this.player = player.player - } - }, - Modifier - .width(width) - .combinedClickable( - onLongClick = { showMenu.value = true }, - onClick = { if (player.player.playWhenReady) stop() else onClick() } - ) - ) - if (showPreview.value) { - ImageView(preview, showMenu, onClick) - PlayButton(brokenVideo, onLongClick = { showMenu.value = true }, play) - } - DurationProgress(file, videoPlaying, duration, progress/*, soundEnabled*/) - } -} - -@Composable -private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, onClick: () -> Unit) { - Surface( - Modifier.align(Alignment.Center), - color = Color.Black.copy(alpha = 0.25f), - shape = RoundedCornerShape(percent = 50) - ) { - Box( - Modifier - .defaultMinSize(minWidth = 40.dp, minHeight = 40.dp) - .combinedClickable(onClick = onClick, onLongClick = onLongClick), - contentAlignment = Alignment.Center - ) { - Icon( - painterResource(MR.images.ic_play_arrow_filled), - contentDescription = null, - tint = if (error) WarningOrange else Color.White - ) - } - } -} - -@Composable -private fun DurationProgress(file: CIFile, playing: MutableState, duration: MutableState, progress: MutableState/*, soundEnabled: MutableState*/) { - if (duration.value > 0L || progress.value > 0) { - Row { - Box( - Modifier - .padding(DEFAULT_PADDING_HALF) - .background(Color.Black.copy(alpha = 0.35f), RoundedCornerShape(percent = 50)) - .padding(vertical = 2.dp, horizontal = 4.dp) - ) { - val time = if (progress.value > 0) progress.value else duration.value - val timeStr = durationText((time / 1000).toInt()) - val width = if (timeStr.length <= 5) 44 else 50 - Text( - timeStr, - Modifier.widthIn(min = with(LocalDensity.current) { width.sp.toDp() }).padding(horizontal = 4.dp), - fontSize = 13.sp, - color = Color.White - ) - /*if (!soundEnabled.value) { - Icon(painterResource(MR.images.ic_volume_off_filled), null, - Modifier.padding(start = 5.dp).size(10.dp), - tint = Color.White - ) - }*/ - } - if (!playing.value) { - Box( - Modifier - .padding(top = DEFAULT_PADDING_HALF) - .background(Color.Black.copy(alpha = 0.35f), RoundedCornerShape(percent = 50)) - .padding(vertical = 2.dp, horizontal = 4.dp) - ) { - Text( - formatBytes(file.fileSize), - Modifier.padding(horizontal = 4.dp), - fontSize = 13.sp, - color = Color.White - ) - } - } - } - } -} - -@Composable -private fun ImageView(preview: Bitmap, showMenu: MutableState, onClick: () -> Unit) { - val windowWidth = LocalWindowWidth() - val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp } - Image( - preview.asImageBitmap(), - contentDescription = stringResource(MR.strings.video_descr), - modifier = Modifier + }, + Modifier .width(width) .combinedClickable( - onLongClick = { showMenu.value = true }, - onClick = onClick - ), - contentScale = ContentScale.FillWidth, + onLongClick = onLongClick, + onClick = { if (player.player.playWhenReady) stop() else onClick() } + ) ) } -@Composable -private fun LocalWindowWidth(): Dp { +@Composable fun LocalWindowWidth(): Dp { val view = LocalView.current val density = LocalDensity.current.density return remember { @@ -242,98 +41,3 @@ private fun LocalWindowWidth(): Dp { (rect.width() / density).dp } } - -@Composable -private fun progressIndicator() { - CircularProgressIndicator( - Modifier.size(16.dp), - color = Color.White, - strokeWidth = 2.dp - ) -} - -@Composable -private fun fileIcon(icon: Painter, stringId: StringResource) { - Icon( - icon, - stringResource(stringId), - Modifier.fillMaxSize(), - tint = Color.White - ) -} - -@Composable -private fun progressCircle(progress: Long, total: Long) { - val angle = 360f * (progress.toDouble() / total.toDouble()).toFloat() - val strokeWidth = with(LocalDensity.current) { 2.dp.toPx() } - val strokeColor = Color.White - Surface( - Modifier.drawRingModifier(angle, strokeColor, strokeWidth), - color = Color.Transparent, - shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)) - ) { - Box(Modifier.size(16.dp)) - } -} - -@Composable -private fun loadingIndicator(file: CIFile?) { - if (file != null) { - Box( - Modifier - .padding(8.dp) - .size(20.dp), - contentAlignment = Alignment.Center - ) { - when (file.fileStatus) { - is CIFileStatus.SndStored -> - when (file.fileProtocol) { - FileProtocol.XFTP -> progressIndicator() - FileProtocol.SMP -> {} - } - is CIFileStatus.SndTransfer -> - when (file.fileProtocol) { - FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal) - FileProtocol.SMP -> progressIndicator() - } - is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_video_snd_complete) - is CIFileStatus.SndCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - is CIFileStatus.SndError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_video_asked_to_receive) - is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_video) - is CIFileStatus.RcvTransfer -> - if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) { - progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal) - } else { - progressIndicator() - } - is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) - else -> {} - } - } - } -} - -private fun fileSizeValid(file: CIFile?): Boolean { - if (file != null) { - return file.fileSize <= getMaxFileSize(file.fileProtocol) - } - return false -} - -private fun receiveFileIfValidSize(file: CIFile, receiveFile: (Long) -> Unit) { - if (fileSizeValid(file)) { - receiveFile(file.fileId) - } else { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.large_file), - String.format(generalGetString(MR.strings.contact_sent_large_file), formatBytes(getMaxFileSize(file.fileProtocol))) - ) - } -} - -private fun videoViewFullWidth(windowWidth: Dp): Dp { - val approximatePadding = 100.dp - return minOf(1000.dp, windowWidth - approximatePadding) -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt index 8d331c2276..de1cd602d3 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.* import androidx.compose.ui.unit.* import chat.simplex.app.model.* +import chat.simplex.app.platform.getLoadedFilePath import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.res.MR diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt new file mode 100644 index 0000000000..2e2c1676e5 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt @@ -0,0 +1,533 @@ +package chat.simplex.app.views.chat.item + +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.* +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.app.model.* +import chat.simplex.app.platform.* +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.chat.* +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import kotlinx.datetime.Clock + +// TODO refactor so that FramedItemView can show all CIContent items if they're deleted (see Swift code) + +@Composable +fun ChatItemView( + cInfo: ChatInfo, + cItem: ChatItem, + composeState: MutableState, + imageProvider: (() -> ImageGalleryProvider)? = null, + showMember: Boolean = false, + useLinkPreviews: Boolean, + linkMode: SimplexLinkMode, + deleteMessage: (Long, CIDeleteMode) -> Unit, + receiveFile: (Long) -> Unit, + cancelFile: (Long) -> Unit, + joinGroup: (Long) -> Unit, + acceptCall: (Contact) -> Unit, + scrollToItem: (Long) -> Unit, + acceptFeature: (Contact, ChatFeature, Int?) -> Unit, + setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit, + showItemDetails: (ChatInfo, ChatItem) -> Unit, +) { + val uriHandler = LocalUriHandler.current + val sent = cItem.chatDir.sent + val alignment = if (sent) Alignment.CenterEnd else Alignment.CenterStart + val showMenu = remember { mutableStateOf(false) } + val revealed = remember { mutableStateOf(false) } + val fullDeleteAllowed = remember(cInfo) { cInfo.featureEnabled(ChatFeature.FullDelete) } + val onLinkLongClick = { _: String -> showMenu.value = true } + val live = composeState.value.liveMessage != null + + Box( + modifier = Modifier + .padding(bottom = 4.dp) + .fillMaxWidth(), + contentAlignment = alignment, + ) { + val onClick = { + when (cItem.meta.itemStatus) { + is CIStatus.SndErrorAuth -> { + showMsgDeliveryErrorAlert(generalGetString(MR.strings.message_delivery_error_desc)) + } + is CIStatus.SndError -> { + showMsgDeliveryErrorAlert(generalGetString(MR.strings.unknown_error) + ": ${cItem.meta.itemStatus.agentError}") + } + else -> {} + } + } + + @Composable + fun ChatItemReactions() { + Row { + cItem.reactions.forEach { r -> + var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp)) + if (cInfo.featureEnabled(ChatFeature.Reactions) && (cItem.allowAddReaction || r.userReacted)) { + modifier = modifier.clickable { + setReaction(cInfo, cItem, !r.userReacted, r.reaction) + } + } + Row(modifier.padding(2.dp)) { + Text(r.reaction.text, fontSize = 12.sp) + if (r.totalReacted > 1) { + Spacer(Modifier.width(4.dp)) + Text("${r.totalReacted}", + fontSize = 11.5.sp, + fontWeight = if (r.userReacted) FontWeight.Bold else FontWeight.Normal, + color = if (r.userReacted) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + ) + } + } + } + } + } + + Column(horizontalAlignment = if (cItem.chatDir.sent) Alignment.End else Alignment.Start) { + Column( + Modifier + .clip(RoundedCornerShape(18.dp)) + .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick), + ) { + @Composable + fun framedItemView() { + FramedItemView(cInfo, cItem, uriHandler, imageProvider, showMember = showMember, linkMode = linkMode, showMenu, receiveFile, onLinkLongClick, scrollToItem) + } + + fun deleteMessageQuestionText(): String { + return if (fullDeleteAllowed) { + generalGetString(MR.strings.delete_message_cannot_be_undone_warning) + } else { + generalGetString(MR.strings.delete_message_mark_deleted_warning) + } + } + + fun moderateMessageQuestionText(): String { + return if (fullDeleteAllowed) { + generalGetString(MR.strings.moderate_message_will_be_deleted_warning) + } else { + generalGetString(MR.strings.moderate_message_will_be_marked_warning) + } + } + + @Composable + fun MsgReactionsMenu() { + val rs = MsgReaction.values.mapNotNull { r -> + if (null == cItem.reactions.find { it.userReacted && it.reaction.text == r.text }) { + r + } else { + null + } + } + if (rs.isNotEmpty()) { + Row(modifier = Modifier.padding(horizontal = DEFAULT_PADDING).horizontalScroll(rememberScrollState())) { + rs.forEach() { r -> + Box( + Modifier.size(36.dp).clickable { + setReaction(cInfo, cItem, true, r) + showMenu.value = false + }, + contentAlignment = Alignment.Center + ) { + Text(r.text) + } + } + } + } + } + + @Composable + fun MsgContentItemDropdownMenu() { + DefaultDropdownMenu(showMenu) { + if (cInfo.featureEnabled(ChatFeature.Reactions) && cItem.allowAddReaction) { + MsgReactionsMenu() + } + if (cItem.meta.itemDeleted == null && !live) { + ItemAction(stringResource(MR.strings.reply_verb), painterResource(MR.images.ic_reply), onClick = { + if (composeState.value.editing) { + composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) + } else { + composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) + } + showMenu.value = false + }) + } + ItemAction(stringResource(MR.strings.share_verb), painterResource(MR.images.ic_share), onClick = { + val filePath = getLoadedFilePath(cItem.file) + when { + filePath != null -> shareFile(cItem.text, filePath) + else -> shareText(cItem.content.text) + } + showMenu.value = false + }) + ItemAction(stringResource(MR.strings.copy_verb), painterResource(MR.images.ic_content_copy), onClick = { + copyText(cItem.content.text) + showMenu.value = false + }) + if (cItem.content.msgContent is MsgContent.MCImage || cItem.content.msgContent is MsgContent.MCVideo || cItem.content.msgContent is MsgContent.MCFile || cItem.content.msgContent is MsgContent.MCVoice) { + val filePath = getLoadedFilePath(cItem.file) + if (filePath != null) { + SaveContentItemAction(cItem, showMenu) + } + } + if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice && !live) { + ItemAction(stringResource(MR.strings.edit_verb), painterResource(MR.images.ic_edit_filled), onClick = { + composeState.value = ComposeState(editingItem = cItem, useLinkPreviews = useLinkPreviews) + showMenu.value = false + }) + } + if (cItem.meta.itemDeleted != null && revealed.value) { + ItemAction( + stringResource(MR.strings.hide_verb), + painterResource(MR.images.ic_visibility_off), + onClick = { + revealed.value = false + showMenu.value = false + } + ) + } + if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null) { + CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction) + } + ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) + if (!(live && cItem.meta.isLive)) { + DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage) + } + val groupInfo = cItem.memberToModerate(cInfo)?.first + if (groupInfo != null) { + ModerateItemAction(cItem, questionText = moderateMessageQuestionText(), showMenu, deleteMessage) + } + } + } + + @Composable + fun MarkedDeletedItemDropdownMenu() { + DefaultDropdownMenu(showMenu) { + if (!cItem.isDeletedContent) { + ItemAction( + stringResource(MR.strings.reveal_verb), + painterResource(MR.images.ic_visibility), + onClick = { + revealed.value = true + showMenu.value = false + } + ) + ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) + } + DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage) + } + } + + @Composable + fun ContentItem() { + val mc = cItem.content.msgContent + if (cItem.meta.itemDeleted != null && !revealed.value) { + MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember) + MarkedDeletedItemDropdownMenu() + } else { + if (cItem.quotedItem == null && cItem.meta.itemDeleted == null && !cItem.meta.isLive) { + if (mc is MsgContent.MCText && isShortEmoji(cItem.content.text)) { + EmojiItemView(cItem, cInfo.timedMessagesTTL) + } else if (mc is MsgContent.MCVoice && cItem.content.text.isEmpty()) { + CIVoiceView(mc.duration, cItem.file, cItem.meta.itemEdited, cItem.chatDir.sent, hasText = false, cItem, cInfo.timedMessagesTTL, longClick = { onLinkLongClick("") }, receiveFile) + } else { + framedItemView() + } + } else { + framedItemView() + } + MsgContentItemDropdownMenu() + } + } + + @Composable fun DeletedItem() { + DeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember) + DefaultDropdownMenu(showMenu) { + DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage) + } + } + + @Composable fun CallItem(status: CICallStatus, duration: Int) { + CICallItemView(cInfo, cItem, status, duration, acceptCall) + } + + @Composable + fun ModeratedItem() { + MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember) + DefaultDropdownMenu(showMenu) { + DeleteItemAction(cItem, showMenu, questionText = generalGetString(MR.strings.delete_message_cannot_be_undone_warning), deleteMessage) + } + } + + when (val c = cItem.content) { + is CIContent.SndMsgContent -> ContentItem() + is CIContent.RcvMsgContent -> ContentItem() + is CIContent.SndDeleted -> DeletedItem() + is CIContent.RcvDeleted -> DeletedItem() + is CIContent.SndCall -> CallItem(c.status, c.duration) + is CIContent.RcvCall -> CallItem(c.status, c.duration) + is CIContent.RcvIntegrityError -> IntegrityErrorItemView(c.msgError, cItem, cInfo.timedMessagesTTL, showMember = showMember) + is CIContent.RcvDecryptionError -> CIRcvDecryptionError(c.msgDecryptError, c.msgCount, cItem, cInfo.timedMessagesTTL, showMember = showMember) + is CIContent.RcvGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito) + is CIContent.SndGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito) + is CIContent.RcvGroupEventContent -> CIEventView(cItem) + is CIContent.SndGroupEventContent -> CIEventView(cItem) + is CIContent.RcvConnEventContent -> CIEventView(cItem) + is CIContent.SndConnEventContent -> CIEventView(cItem) + is CIContent.RcvChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor) + is CIContent.SndChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor) + is CIContent.RcvChatPreference -> { + val ct = if (cInfo is ChatInfo.Direct) cInfo.contact else null + CIFeaturePreferenceView(cItem, ct, c.feature, c.allowed, acceptFeature) + } + is CIContent.SndChatPreference -> CIChatFeatureView(cItem, c.feature, MaterialTheme.colors.secondary, icon = c.feature.icon,) + is CIContent.RcvGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor) + is CIContent.SndGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor) + is CIContent.RcvChatFeatureRejected -> CIChatFeatureView(cItem, c.feature, Color.Red) + is CIContent.RcvGroupFeatureRejected -> CIChatFeatureView(cItem, c.groupFeature, Color.Red) + is CIContent.SndModerated -> ModeratedItem() + is CIContent.RcvModerated -> ModeratedItem() + is CIContent.InvalidJSON -> CIInvalidJSONView(c.json) + } + } + + if (cItem.content.msgContent != null && (cItem.meta.itemDeleted == null || revealed.value) && cItem.reactions.isNotEmpty()) { + ChatItemReactions() + } + } + } +} + +@Composable +fun CancelFileItemAction( + fileId: Long, + showMenu: MutableState, + cancelFile: (Long) -> Unit, + cancelAction: CancelAction +) { + ItemAction( + stringResource(cancelAction.uiActionId), + painterResource(MR.images.ic_close), + onClick = { + showMenu.value = false + cancelFileAlertDialog(fileId, cancelFile = cancelFile, cancelAction = cancelAction) + }, + color = Color.Red + ) +} + +@Composable +fun ItemInfoAction( + cInfo: ChatInfo, + cItem: ChatItem, + showItemDetails: (ChatInfo, ChatItem) -> Unit, + showMenu: MutableState +) { + ItemAction( + stringResource(MR.strings.info_menu), + painterResource(MR.images.ic_info), + onClick = { + showItemDetails(cInfo, cItem) + showMenu.value = false + } + ) +} + + +@Composable +fun DeleteItemAction( + cItem: ChatItem, + showMenu: MutableState, + questionText: String, + deleteMessage: (Long, CIDeleteMode) -> Unit +) { + ItemAction( + stringResource(MR.strings.delete_verb), + painterResource(MR.images.ic_delete), + onClick = { + showMenu.value = false + deleteMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage) + }, + color = Color.Red + ) +} + +@Composable +fun ModerateItemAction( + cItem: ChatItem, + questionText: String, + showMenu: MutableState, + deleteMessage: (Long, CIDeleteMode) -> Unit +) { + ItemAction( + stringResource(MR.strings.moderate_verb), + painterResource(MR.images.ic_flag), + onClick = { + showMenu.value = false + moderateMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage) + }, + color = Color.Red + ) +} + +@Composable +fun ItemAction(text: String, icon: Painter, onClick: () -> Unit, color: Color = Color.Unspecified) { + val finalColor = if (color == Color.Unspecified) { + if (isInDarkTheme()) MenuTextColorDark else Color.Black + } else color + DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text, + modifier = Modifier + .fillMaxWidth() + .weight(1F) + .padding(end = 15.dp), + color = finalColor + ) + Icon(icon, text, tint = finalColor) + } + } +} + +@Composable +fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Color = Color.Unspecified) { + val finalColor = if (color == Color.Unspecified) { + if (isInDarkTheme()) MenuTextColorDark else Color.Black + } else color + DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text, + modifier = Modifier + .fillMaxWidth() + .weight(1F) + .padding(end = 15.dp), + color = finalColor + ) + Icon(icon, text, tint = finalColor) + } + } +} + +fun cancelFileAlertDialog(fileId: Long, cancelFile: (Long) -> Unit, cancelAction: CancelAction) { + AlertManager.shared.showAlertDialog( + title = generalGetString(cancelAction.alert.titleId), + text = generalGetString(cancelAction.alert.messageId), + confirmText = generalGetString(cancelAction.alert.confirmId), + destructive = true, + onConfirm = { + cancelFile(fileId) + } + ) +} + +fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit) { + AlertManager.shared.showAlertDialogButtons( + title = generalGetString(MR.strings.delete_message__question), + text = questionText, + buttons = { + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 2.dp), + horizontalArrangement = Arrangement.Center, + ) { + TextButton(onClick = { + deleteMessage(chatItem.id, CIDeleteMode.cidmInternal) + AlertManager.shared.hideAlert() + }) { Text(stringResource(MR.strings.for_me_only), color = MaterialTheme.colors.error) } + if (chatItem.meta.editable) { + Spacer(Modifier.padding(horizontal = 4.dp)) + TextButton(onClick = { + deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast) + AlertManager.shared.hideAlert() + }) { Text(stringResource(MR.strings.for_everybody), color = MaterialTheme.colors.error) } + } + } + } + ) +} + +fun moderateMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.delete_member_message__question), + text = questionText, + confirmText = generalGetString(MR.strings.delete_verb), + destructive = true, + onConfirm = { + deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast) + } + ) +} + +private fun showMsgDeliveryErrorAlert(description: String) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.message_delivery_error_title), + text = description, + ) +} + +@Preview +@Composable +fun PreviewChatItemView() { + SimpleXTheme { + ChatItemView( + ChatInfo.Direct.sampleData, + ChatItem.getSampleData( + 1, CIDirection.DirectSnd(), Clock.System.now(), "hello" + ), + useLinkPreviews = true, + linkMode = SimplexLinkMode.DESCRIPTION, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, + deleteMessage = { _, _ -> }, + receiveFile = {}, + cancelFile = {}, + joinGroup = {}, + acceptCall = { _ -> }, + scrollToItem = {}, + acceptFeature = { _, _, _ -> }, + setReaction = { _, _, _, _ -> }, + showItemDetails = { _, _ -> }, + ) + } +} + +@Preview +@Composable +fun PreviewChatItemViewDeletedContent() { + SimpleXTheme { + ChatItemView( + ChatInfo.Direct.sampleData, + ChatItem.getDeletedContentSampleData(), + useLinkPreviews = true, + linkMode = SimplexLinkMode.DESCRIPTION, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, + deleteMessage = { _, _ -> }, + receiveFile = {}, + cancelFile = {}, + joinGroup = {}, + acceptCall = { _ -> }, + scrollToItem = {}, + acceptFeature = { _, _, _ -> }, + setReaction = { _, _, _, _ -> }, + showItemDetails = { _, _ -> }, + ) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt index 93a4670dfe..ef0eb89c86 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt @@ -2,551 +2,32 @@ package chat.simplex.app.views.chat.item import android.Manifest import android.os.Build -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.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import chat.simplex.app.model.ChatItem +import chat.simplex.app.model.MsgContent +import chat.simplex.app.platform.saveImage +import chat.simplex.res.MR +import com.google.accompanist.permissions.rememberPermissionState import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.* -import chat.simplex.app.views.helpers.* -import com.google.accompanist.permissions.rememberPermissionState -import chat.simplex.res.MR -import kotlinx.datetime.Clock - -// TODO refactor so that FramedItemView can show all CIContent items if they're deleted (see Swift code) @Composable -fun ChatItemView( - cInfo: ChatInfo, - cItem: ChatItem, - composeState: MutableState, - imageProvider: (() -> ImageGalleryProvider)? = null, - showMember: Boolean = false, - useLinkPreviews: Boolean, - linkMode: SimplexLinkMode, - deleteMessage: (Long, CIDeleteMode) -> Unit, - receiveFile: (Long) -> Unit, - cancelFile: (Long) -> Unit, - joinGroup: (Long) -> Unit, - acceptCall: (Contact) -> Unit, - scrollToItem: (Long) -> Unit, - acceptFeature: (Contact, ChatFeature, Int?) -> Unit, - setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit, - showItemDetails: (ChatInfo, ChatItem) -> Unit, -) { - val context = LocalContext.current - val uriHandler = LocalUriHandler.current - val sent = cItem.chatDir.sent - val alignment = if (sent) Alignment.CenterEnd else Alignment.CenterStart - val showMenu = remember { mutableStateOf(false) } - val revealed = remember { mutableStateOf(false) } - val fullDeleteAllowed = remember(cInfo) { cInfo.featureEnabled(ChatFeature.FullDelete) } +fun SaveContentItemAction(cItem: ChatItem, showMenu: MutableState) { val saveFileLauncher = rememberSaveFileLauncher(ciFile = cItem.file) - val onLinkLongClick = { _: String -> showMenu.value = true } - val live = composeState.value.liveMessage != null - - Box( - modifier = Modifier - .padding(bottom = 4.dp) - .fillMaxWidth(), - contentAlignment = alignment, - ) { - val onClick = { - when (cItem.meta.itemStatus) { - is CIStatus.SndErrorAuth -> { - showMsgDeliveryErrorAlert(generalGetString(MR.strings.message_delivery_error_desc)) - } - is CIStatus.SndError -> { - showMsgDeliveryErrorAlert(generalGetString(MR.strings.unknown_error) + ": ${cItem.meta.itemStatus.agentError}") - } - else -> {} - } - } - - @Composable - fun ChatItemReactions() { - Row { - cItem.reactions.forEach { r -> - var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp)) - if (cInfo.featureEnabled(ChatFeature.Reactions) && (cItem.allowAddReaction || r.userReacted)) { - modifier = modifier.clickable { - setReaction(cInfo, cItem, !r.userReacted, r.reaction) - } - } - Row(modifier.padding(2.dp)) { - Text(r.reaction.text, fontSize = 12.sp) - if (r.totalReacted > 1) { - Spacer(Modifier.width(4.dp)) - Text("${r.totalReacted}", - fontSize = 11.5.sp, - fontWeight = if (r.userReacted) FontWeight.Bold else FontWeight.Normal, - color = if (r.userReacted) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - ) - } - } + val writePermissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE) + ItemAction(stringResource(MR.strings.save_verb), painterResource(MR.images.ic_download), onClick = { + when (cItem.content.msgContent) { + is MsgContent.MCImage -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.hasPermission) { + saveImage(cItem.file) + } else { + writePermissionState.launchPermissionRequest() } } + is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> saveFileLauncher.launch(cItem.file?.fileName) + else -> {} } - - Column(horizontalAlignment = if (cItem.chatDir.sent) Alignment.End else Alignment.Start) { - Column( - Modifier - .clip(RoundedCornerShape(18.dp)) - .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick), - ) { - @Composable - fun framedItemView() { - FramedItemView(cInfo, cItem, uriHandler, imageProvider, showMember = showMember, linkMode = linkMode, showMenu, receiveFile, onLinkLongClick, scrollToItem) - } - - fun deleteMessageQuestionText(): String { - return if (fullDeleteAllowed) { - generalGetString(MR.strings.delete_message_cannot_be_undone_warning) - } else { - generalGetString(MR.strings.delete_message_mark_deleted_warning) - } - } - - fun moderateMessageQuestionText(): String { - return if (fullDeleteAllowed) { - generalGetString(MR.strings.moderate_message_will_be_deleted_warning) - } else { - generalGetString(MR.strings.moderate_message_will_be_marked_warning) - } - } - - @Composable - fun MsgReactionsMenu() { - val rs = MsgReaction.values.mapNotNull { r -> - if (null == cItem.reactions.find { it.userReacted && it.reaction.text == r.text }) { - r - } else { - null - } - } - if (rs.isNotEmpty()) { - Row(modifier = Modifier.padding(horizontal = DEFAULT_PADDING).horizontalScroll(rememberScrollState())) { - rs.forEach() { r -> - Box( - Modifier.size(36.dp).clickable { - setReaction(cInfo, cItem, true, r) - showMenu.value = false - }, - contentAlignment = Alignment.Center - ) { - Text(r.text) - } - } - } - } - } - - @Composable - fun MsgContentItemDropdownMenu() { - DefaultDropdownMenu(showMenu) { - if (cInfo.featureEnabled(ChatFeature.Reactions) && cItem.allowAddReaction) { - MsgReactionsMenu() - } - if (cItem.meta.itemDeleted == null && !live) { - ItemAction(stringResource(MR.strings.reply_verb), painterResource(MR.images.ic_reply), onClick = { - if (composeState.value.editing) { - composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) - } else { - composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) - } - showMenu.value = false - }) - } - ItemAction(stringResource(MR.strings.share_verb), painterResource(MR.images.ic_share), onClick = { - val filePath = getLoadedFilePath(cItem.file) - when { - filePath != null -> shareFile(cItem.text, filePath) - else -> shareText(cItem.content.text) - } - showMenu.value = false - }) - ItemAction(stringResource(MR.strings.copy_verb), painterResource(MR.images.ic_content_copy), onClick = { - copyText(cItem.content.text) - showMenu.value = false - }) - if (cItem.content.msgContent is MsgContent.MCImage || cItem.content.msgContent is MsgContent.MCVideo || cItem.content.msgContent is MsgContent.MCFile || cItem.content.msgContent is MsgContent.MCVoice) { - val filePath = getLoadedFilePath(cItem.file) - if (filePath != null) { - val writePermissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE) - ItemAction(stringResource(MR.strings.save_verb), painterResource(MR.images.ic_download), onClick = { - when (cItem.content.msgContent) { - is MsgContent.MCImage -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.hasPermission) { - saveImage(cItem.file) - } else { - writePermissionState.launchPermissionRequest() - } - } - is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> saveFileLauncher.launch(cItem.file?.fileName) - else -> {} - } - showMenu.value = false - }) - } - } - if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice && !live) { - ItemAction(stringResource(MR.strings.edit_verb), painterResource(MR.images.ic_edit_filled), onClick = { - composeState.value = ComposeState(editingItem = cItem, useLinkPreviews = useLinkPreviews) - showMenu.value = false - }) - } - if (cItem.meta.itemDeleted != null && revealed.value) { - ItemAction( - stringResource(MR.strings.hide_verb), - painterResource(MR.images.ic_visibility_off), - onClick = { - revealed.value = false - showMenu.value = false - } - ) - } - if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null) { - CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction) - } - ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) - if (!(live && cItem.meta.isLive)) { - DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage) - } - val groupInfo = cItem.memberToModerate(cInfo)?.first - if (groupInfo != null) { - ModerateItemAction(cItem, questionText = moderateMessageQuestionText(), showMenu, deleteMessage) - } - } - } - - @Composable - fun MarkedDeletedItemDropdownMenu() { - DefaultDropdownMenu(showMenu) { - if (!cItem.isDeletedContent) { - ItemAction( - stringResource(MR.strings.reveal_verb), - painterResource(MR.images.ic_visibility), - onClick = { - revealed.value = true - showMenu.value = false - } - ) - ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) - } - DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage) - } - } - - @Composable - fun ContentItem() { - val mc = cItem.content.msgContent - if (cItem.meta.itemDeleted != null && !revealed.value) { - MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember) - MarkedDeletedItemDropdownMenu() - } else { - if (cItem.quotedItem == null && cItem.meta.itemDeleted == null && !cItem.meta.isLive) { - if (mc is MsgContent.MCText && isShortEmoji(cItem.content.text)) { - EmojiItemView(cItem, cInfo.timedMessagesTTL) - } else if (mc is MsgContent.MCVoice && cItem.content.text.isEmpty()) { - CIVoiceView(mc.duration, cItem.file, cItem.meta.itemEdited, cItem.chatDir.sent, hasText = false, cItem, cInfo.timedMessagesTTL, longClick = { onLinkLongClick("") }, receiveFile) - } else { - framedItemView() - } - } else { - framedItemView() - } - MsgContentItemDropdownMenu() - } - } - - @Composable fun DeletedItem() { - DeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember) - DefaultDropdownMenu(showMenu) { - DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage) - } - } - - @Composable fun CallItem(status: CICallStatus, duration: Int) { - CICallItemView(cInfo, cItem, status, duration, acceptCall) - } - - @Composable - fun ModeratedItem() { - MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, showMember = showMember) - DefaultDropdownMenu(showMenu) { - DeleteItemAction(cItem, showMenu, questionText = generalGetString(MR.strings.delete_message_cannot_be_undone_warning), deleteMessage) - } - } - - when (val c = cItem.content) { - is CIContent.SndMsgContent -> ContentItem() - is CIContent.RcvMsgContent -> ContentItem() - is CIContent.SndDeleted -> DeletedItem() - is CIContent.RcvDeleted -> DeletedItem() - is CIContent.SndCall -> CallItem(c.status, c.duration) - is CIContent.RcvCall -> CallItem(c.status, c.duration) - is CIContent.RcvIntegrityError -> IntegrityErrorItemView(c.msgError, cItem, cInfo.timedMessagesTTL, showMember = showMember) - is CIContent.RcvDecryptionError -> CIRcvDecryptionError(c.msgDecryptError, c.msgCount, cItem, cInfo.timedMessagesTTL, showMember = showMember) - is CIContent.RcvGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito) - is CIContent.SndGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito) - is CIContent.RcvGroupEventContent -> CIEventView(cItem) - is CIContent.SndGroupEventContent -> CIEventView(cItem) - is CIContent.RcvConnEventContent -> CIEventView(cItem) - is CIContent.SndConnEventContent -> CIEventView(cItem) - is CIContent.RcvChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor) - is CIContent.SndChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor) - is CIContent.RcvChatPreference -> { - val ct = if (cInfo is ChatInfo.Direct) cInfo.contact else null - CIFeaturePreferenceView(cItem, ct, c.feature, c.allowed, acceptFeature) - } - is CIContent.SndChatPreference -> CIChatFeatureView(cItem, c.feature, MaterialTheme.colors.secondary, icon = c.feature.icon,) - is CIContent.RcvGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor) - is CIContent.SndGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor) - is CIContent.RcvChatFeatureRejected -> CIChatFeatureView(cItem, c.feature, Color.Red) - is CIContent.RcvGroupFeatureRejected -> CIChatFeatureView(cItem, c.groupFeature, Color.Red) - is CIContent.SndModerated -> ModeratedItem() - is CIContent.RcvModerated -> ModeratedItem() - is CIContent.InvalidJSON -> CIInvalidJSONView(c.json) - } - } - - if (cItem.content.msgContent != null && (cItem.meta.itemDeleted == null || revealed.value) && cItem.reactions.isNotEmpty()) { - ChatItemReactions() - } - } - } -} - -@Composable -fun CancelFileItemAction( - fileId: Long, - showMenu: MutableState, - cancelFile: (Long) -> Unit, - cancelAction: CancelAction -) { - ItemAction( - stringResource(cancelAction.uiActionId), - painterResource(MR.images.ic_close), - onClick = { - showMenu.value = false - cancelFileAlertDialog(fileId, cancelFile = cancelFile, cancelAction = cancelAction) - }, - color = Color.Red - ) -} - -@Composable -fun ItemInfoAction( - cInfo: ChatInfo, - cItem: ChatItem, - showItemDetails: (ChatInfo, ChatItem) -> Unit, - showMenu: MutableState -) { - ItemAction( - stringResource(MR.strings.info_menu), - painterResource(MR.images.ic_info), - onClick = { - showItemDetails(cInfo, cItem) - showMenu.value = false - } - ) -} - - -@Composable -fun DeleteItemAction( - cItem: ChatItem, - showMenu: MutableState, - questionText: String, - deleteMessage: (Long, CIDeleteMode) -> Unit -) { - ItemAction( - stringResource(MR.strings.delete_verb), - painterResource(MR.images.ic_delete), - onClick = { - showMenu.value = false - deleteMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage) - }, - color = Color.Red - ) -} - -@Composable -fun ModerateItemAction( - cItem: ChatItem, - questionText: String, - showMenu: MutableState, - deleteMessage: (Long, CIDeleteMode) -> Unit -) { - ItemAction( - stringResource(MR.strings.moderate_verb), - painterResource(MR.images.ic_flag), - onClick = { - showMenu.value = false - moderateMessageAlertDialog(cItem, questionText, deleteMessage = deleteMessage) - }, - color = Color.Red - ) -} - -@Composable -fun ItemAction(text: String, icon: Painter, onClick: () -> Unit, color: Color = Color.Unspecified) { - val finalColor = if (color == Color.Unspecified) { - if (isInDarkTheme()) MenuTextColorDark else Color.Black - } else color - DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text, - modifier = Modifier - .fillMaxWidth() - .weight(1F) - .padding(end = 15.dp), - color = finalColor - ) - Icon(icon, text, tint = finalColor) - } - } -} - -@Composable -fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Color = Color.Unspecified) { - val finalColor = if (color == Color.Unspecified) { - if (isInDarkTheme()) MenuTextColorDark else Color.Black - } else color - DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text, - modifier = Modifier - .fillMaxWidth() - .weight(1F) - .padding(end = 15.dp), - color = finalColor - ) - Icon(icon, text, tint = finalColor) - } - } -} - -fun cancelFileAlertDialog(fileId: Long, cancelFile: (Long) -> Unit, cancelAction: CancelAction) { - AlertManager.shared.showAlertDialog( - title = generalGetString(cancelAction.alert.titleId), - text = generalGetString(cancelAction.alert.messageId), - confirmText = generalGetString(cancelAction.alert.confirmId), - destructive = true, - onConfirm = { - cancelFile(fileId) - } - ) -} - -fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit) { - AlertManager.shared.showAlertDialogButtons( - title = generalGetString(MR.strings.delete_message__question), - text = questionText, - buttons = { - Row( - Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 2.dp), - horizontalArrangement = Arrangement.Center, - ) { - TextButton(onClick = { - deleteMessage(chatItem.id, CIDeleteMode.cidmInternal) - AlertManager.shared.hideAlert() - }) { Text(stringResource(MR.strings.for_me_only), color = MaterialTheme.colors.error) } - if (chatItem.meta.editable) { - Spacer(Modifier.padding(horizontal = 4.dp)) - TextButton(onClick = { - deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast) - AlertManager.shared.hideAlert() - }) { Text(stringResource(MR.strings.for_everybody), color = MaterialTheme.colors.error) } - } - } - } - ) -} - -fun moderateMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.delete_member_message__question), - text = questionText, - confirmText = generalGetString(MR.strings.delete_verb), - destructive = true, - onConfirm = { - deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast) - } - ) -} - -private fun showMsgDeliveryErrorAlert(description: String) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.message_delivery_error_title), - text = description, - ) -} - -@Preview -@Composable -fun PreviewChatItemView() { - SimpleXTheme { - ChatItemView( - ChatInfo.Direct.sampleData, - ChatItem.getSampleData( - 1, CIDirection.DirectSnd(), Clock.System.now(), "hello" - ), - useLinkPreviews = true, - linkMode = SimplexLinkMode.DESCRIPTION, - composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, - deleteMessage = { _, _ -> }, - receiveFile = {}, - cancelFile = {}, - joinGroup = {}, - acceptCall = { _ -> }, - scrollToItem = {}, - acceptFeature = { _, _, _ -> }, - setReaction = { _, _, _, _ -> }, - showItemDetails = { _, _ -> }, - ) - } -} - -@Preview -@Composable -fun PreviewChatItemViewDeletedContent() { - SimpleXTheme { - ChatItemView( - ChatInfo.Direct.sampleData, - ChatItem.getDeletedContentSampleData(), - useLinkPreviews = true, - linkMode = SimplexLinkMode.DESCRIPTION, - composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, - deleteMessage = { _, _ -> }, - receiveFile = {}, - cancelFile = {}, - joinGroup = {}, - acceptCall = { _ -> }, - scrollToItem = {}, - acceptFeature = { _, _, _ -> }, - setReaction = { _, _, _, _ -> }, - showItemDetails = { _, _ -> }, - ) - } + showMenu.value = false + }) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index 6dc7920235..f85db89106 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.tooling.preview.* import androidx.compose.ui.unit.* import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.base64ToBitmap import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.res.MR diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt new file mode 100644 index 0000000000..d39baa29e2 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt @@ -0,0 +1,178 @@ +package chat.simplex.app.views.chat.item + +import android.graphics.Bitmap +import android.net.Uri +import android.os.Build +import android.view.View +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.input.pointer.* +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.* +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.isVisible +import chat.simplex.app.platform.base64ToBitmap +import chat.simplex.app.views.chat.ProviderMedia +import chat.simplex.app.views.helpers.* +import coil.ImageLoader +import coil.compose.rememberAsyncImagePainter +import coil.decode.GifDecoder +import coil.decode.ImageDecoderDecoder +import coil.request.ImageRequest +import coil.size.Size +import com.google.accompanist.pager.* +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout +import com.google.android.exoplayer2.ui.StyledPlayerView +import chat.simplex.res.MR +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import kotlin.math.absoluteValue + +interface ImageGalleryProvider { + val initialIndex: Int + val totalMediaSize: MutableState + fun getMedia(index: Int): ProviderMedia? + fun currentPageChanged(index: Int) + fun scrollToStart() + fun onDismiss(index: Int) +} + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> Unit) { + val provider = remember { imageProvider() } + val pagerState = rememberPagerState(provider.initialIndex) + val goBack = { provider.onDismiss(pagerState.currentPage); close() } + BackHandler(onBack = goBack) + // Pager doesn't ask previous page at initialization step who knows why. By not doing this, prev page is not checked and can be blank, + // which makes this blank page visible for a moment. Prevent it by doing the check ourselves + LaunchedEffect(Unit) { + if (provider.getMedia(provider.initialIndex - 1) == null) { + provider.scrollToStart() + pagerState.scrollToPage(0) + } + } + val scope = rememberCoroutineScope() + val playersToRelease = rememberSaveable { mutableSetOf() } + DisposableEffectOnGone( + whenGone = { playersToRelease.forEach { VideoPlayer.release(it, true, true) } } + ) + HorizontalPager(count = remember { provider.totalMediaSize }.value, state = pagerState) { index -> + Column( + Modifier + .fillMaxSize() + .background(Color.Black) + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = goBack) + ) { + var settledCurrentPage by remember { mutableStateOf(pagerState.currentPage) } + LaunchedEffect(pagerState) { + snapshotFlow { + if (!pagerState.isScrollInProgress) pagerState.currentPage else settledCurrentPage + }.collect { + settledCurrentPage = it + } + } + LaunchedEffect(settledCurrentPage) { + // Make this pager with infinity scrolling with only 3 pages at a time when left and right pages constructs in real time + if (settledCurrentPage != provider.initialIndex) + provider.currentPageChanged(index) + } + val media = provider.getMedia(index) + if (media == null) { + // No such image. Let's shrink total pages size or scroll to start of the list of pages to remove blank page automatically + SideEffect { + scope.launch { + when (settledCurrentPage) { + index - 1 -> provider.totalMediaSize.value = settledCurrentPage + 1 + index + 1 -> { + provider.scrollToStart() + pagerState.scrollToPage(0) + } + } + } + } + } else { + var scale by remember { mutableStateOf(1f) } + var translationX by remember { mutableStateOf(0f) } + var translationY by remember { mutableStateOf(0f) } + var viewWidth by remember { mutableStateOf(0) } + var allowTranslate by remember { mutableStateOf(true) } + LaunchedEffect(settledCurrentPage) { + scale = 1f + translationX = 0f + translationY = 0f + } + val modifier = Modifier + .onGloballyPositioned { + viewWidth = it.size.width + } + .graphicsLayer( + scaleX = scale, + scaleY = scale, + translationX = translationX, + translationY = translationY, + ) + .pointerInput(Unit) { + detectTransformGestures( + { allowTranslate }, + onGesture = { _, pan, gestureZoom, _ -> + scale = (scale * gestureZoom).coerceIn(1f, 20f) + allowTranslate = viewWidth * (scale - 1f) - ((translationX + pan.x * scale).absoluteValue * 2) > 0 + if (scale > 1 && allowTranslate) { + translationX += pan.x * scale + translationY += pan.y * scale + } else if (allowTranslate) { + translationX = 0f + translationY = 0f + } + } + ) + } + .fillMaxSize() + if (media is ProviderMedia.Image) { + val (uri: Uri, imageBitmap: Bitmap) = media + FullScreenImageView(modifier, uri, imageBitmap) + } else if (media is ProviderMedia.Video) { + val preview = remember(media.uri.path) { base64ToBitmap(media.preview) } + VideoView(modifier, media.uri, preview, index == settledCurrentPage) + DisposableEffect(Unit) { + onDispose { playersToRelease.add(media.uri) } + } + } + } + } + } +} + +@Composable +private fun VideoView(modifier: Modifier, uri: Uri, defaultPreview: Bitmap, currentPage: Boolean) { + val context = LocalContext.current + val player = remember(uri) { VideoPlayer.getOrCreate(uri, true, defaultPreview, 0L, true) } + val isCurrentPage = rememberUpdatedState(currentPage) + val play = { + player.play(true) + } + val stop = { + player.stop() + } + LaunchedEffect(Unit) { + player.enableSound(true) + snapshotFlow { isCurrentPage.value } + .distinctUntilChanged() + .collect { if (it) play() else stop() } + } + + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + FullScreenVideoView(player, modifier) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt index 9cfaa3481f..861f2b8f66 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt @@ -4,216 +4,73 @@ import android.graphics.Bitmap import android.net.Uri import android.os.Build import android.view.View -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.* -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.input.pointer.* import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.* -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.* +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible -import chat.simplex.app.R -import chat.simplex.app.views.chat.ProviderMedia -import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.helpers.VideoPlayer +import chat.simplex.res.MR import coil.ImageLoader import coil.compose.rememberAsyncImagePainter import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest import coil.size.Size -import com.google.accompanist.pager.* +import com.google.android.exoplayer2.R import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import com.google.android.exoplayer2.ui.StyledPlayerView -import chat.simplex.res.MR -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.launch -import kotlin.math.absoluteValue +import dev.icerock.moko.resources.compose.stringResource -interface ImageGalleryProvider { - val initialIndex: Int - val totalMediaSize: MutableState - fun getMedia(index: Int): ProviderMedia? - fun currentPageChanged(index: Int) - fun scrollToStart() - fun onDismiss(index: Int) -} - -@OptIn(ExperimentalPagerApi::class) @Composable -fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> Unit) { - val provider = remember { imageProvider() } - val pagerState = rememberPagerState(provider.initialIndex) - val goBack = { provider.onDismiss(pagerState.currentPage); close() } - BackHandler(onBack = goBack) - // Pager doesn't ask previous page at initialization step who knows why. By not doing this, prev page is not checked and can be blank, - // which makes this blank page visible for a moment. Prevent it by doing the check ourselves - LaunchedEffect(Unit) { - if (provider.getMedia(provider.initialIndex - 1) == null) { - provider.scrollToStart() - pagerState.scrollToPage(0) - } - } - val scope = rememberCoroutineScope() - val playersToRelease = rememberSaveable { mutableSetOf() } - DisposableEffectOnGone( - whenGone = { playersToRelease.forEach { VideoPlayer.release(it, true, true) } } - ) - HorizontalPager(count = remember { provider.totalMediaSize }.value, state = pagerState) { index -> - Column( - Modifier - .fillMaxSize() - .background(Color.Black) - .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = goBack) - ) { - var settledCurrentPage by remember { mutableStateOf(pagerState.currentPage) } - LaunchedEffect(pagerState) { - snapshotFlow { - if (!pagerState.isScrollInProgress) pagerState.currentPage else settledCurrentPage - }.collect { - settledCurrentPage = it - } - } - LaunchedEffect(settledCurrentPage) { - // Make this pager with infinity scrolling with only 3 pages at a time when left and right pages constructs in real time - if (settledCurrentPage != provider.initialIndex) - provider.currentPageChanged(index) - } - val media = provider.getMedia(index) - if (media == null) { - // No such image. Let's shrink total pages size or scroll to start of the list of pages to remove blank page automatically - SideEffect { - scope.launch { - when (settledCurrentPage) { - index - 1 -> provider.totalMediaSize.value = settledCurrentPage + 1 - index + 1 -> { - provider.scrollToStart() - pagerState.scrollToPage(0) - } - } - } - } +fun FullScreenImageView(modifier: Modifier, uri: Uri, imageBitmap: Bitmap) { + // I'm making a new instance of imageLoader here because if I use one instance in multiple places + // after end of composition here a GIF from the first instance will be paused automatically which isn't what I want + val imageLoader = ImageLoader.Builder(LocalContext.current) + .components { + if (Build.VERSION.SDK_INT >= 28) { + add(ImageDecoderDecoder.Factory()) } else { - var scale by remember { mutableStateOf(1f) } - var translationX by remember { mutableStateOf(0f) } - var translationY by remember { mutableStateOf(0f) } - var viewWidth by remember { mutableStateOf(0) } - var allowTranslate by remember { mutableStateOf(true) } - LaunchedEffect(settledCurrentPage) { - scale = 1f - translationX = 0f - translationY = 0f - } - val modifier = Modifier - .onGloballyPositioned { - viewWidth = it.size.width - } - .graphicsLayer( - scaleX = scale, - scaleY = scale, - translationX = translationX, - translationY = translationY, - ) - .pointerInput(Unit) { - detectTransformGestures( - { allowTranslate }, - onGesture = { _, pan, gestureZoom, _ -> - scale = (scale * gestureZoom).coerceIn(1f, 20f) - allowTranslate = viewWidth * (scale - 1f) - ((translationX + pan.x * scale).absoluteValue * 2) > 0 - if (scale > 1 && allowTranslate) { - translationX += pan.x * scale - translationY += pan.y * scale - } else if (allowTranslate) { - translationX = 0f - translationY = 0f - } - } - ) - } - .fillMaxSize() - if (media is ProviderMedia.Image) { - val (uri: Uri, imageBitmap: Bitmap) = media - // I'm making a new instance of imageLoader here because if I use one instance in multiple places - // after end of composition here a GIF from the first instance will be paused automatically which isn't what I want - val imageLoader = ImageLoader.Builder(LocalContext.current) - .components { - if (Build.VERSION.SDK_INT >= 28) { - add(ImageDecoderDecoder.Factory()) - } else { - add(GifDecoder.Factory()) - } - } - .build() - Image( - rememberAsyncImagePainter( - ImageRequest.Builder(LocalContext.current).data(data = uri).size(Size.ORIGINAL).build(), - placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil - imageLoader = imageLoader - ), - contentDescription = stringResource(MR.strings.image_descr), - contentScale = ContentScale.Fit, - modifier = modifier, - ) - } else if (media is ProviderMedia.Video) { - val preview = remember(media.uri.path) { base64ToBitmap(media.preview) } - VideoView(modifier, media.uri, preview, index == settledCurrentPage) - DisposableEffect(Unit) { - onDispose { playersToRelease.add(media.uri) } - } - } + add(GifDecoder.Factory()) } } - } + .build() + Image( + rememberAsyncImagePainter( + ImageRequest.Builder(LocalContext.current).data(data = uri).size(Size.ORIGINAL).build(), + placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil + imageLoader = imageLoader + ), + contentDescription = stringResource(MR.strings.image_descr), + contentScale = ContentScale.Fit, + modifier = modifier, + ) } @Composable -private fun VideoView(modifier: Modifier, uri: Uri, defaultPreview: Bitmap, currentPage: Boolean) { - val context = LocalContext.current - val player = remember(uri) { VideoPlayer.getOrCreate(uri, true, defaultPreview, 0L, true) } - val isCurrentPage = rememberUpdatedState(currentPage) - val play = { - player.play(true) - } - val stop = { - player.stop() - } - LaunchedEffect(Unit) { - player.enableSound(true) - snapshotFlow { isCurrentPage.value } - .distinctUntilChanged() - .collect { if (it) play() else stop() } - } - - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - AndroidView( - factory = { ctx -> - StyledPlayerView(ctx).apply { - resizeMode = if (ctx.resources.configuration.screenWidthDp > ctx.resources.configuration.screenHeightDp) { - AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT - } else { - AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH - } - setShowPreviousButton(false) - setShowNextButton(false) - setShowSubtitleButton(false) - setShowVrButton(false) - controllerAutoShow = false - findViewById(com.google.android.exoplayer2.R.id.exo_controls_background).setBackgroundColor(Color.Black.copy(alpha = 0.3f).toArgb()) - findViewById(com.google.android.exoplayer2.R.id.exo_settings).isVisible = false - this.player = player.player +fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier) { + AndroidView( + factory = { ctx -> + StyledPlayerView(ctx).apply { + resizeMode = if (ctx.resources.configuration.screenWidthDp > ctx.resources.configuration.screenHeightDp) { + AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT + } else { + AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH } - }, - modifier - ) - } + setShowPreviousButton(false) + setShowNextButton(false) + setShowSubtitleButton(false) + setShowVrButton(false) + controllerAutoShow = false + findViewById(R.id.exo_controls_background).setBackgroundColor(Color.Black.copy(alpha = 0.3f).toArgb()) + findViewById(R.id.exo_settings).isVisible = false + this.player = player.player + } + }, + modifier + ) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index 3d93cab587..116106853c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -1,5 +1,7 @@ package chat.simplex.app.views.chatlist +import android.net.Uri +import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -23,7 +25,7 @@ import chat.simplex.app.* import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.NewChatSheet +import chat.simplex.app.views.newchat.* import chat.simplex.app.views.onboarding.WhatsNewView import chat.simplex.app.views.onboarding.shouldShowWhatsNew import chat.simplex.app.views.usersettings.SettingsView @@ -306,6 +308,35 @@ private fun ProgressIndicator() { ) } +fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { + Log.d(TAG, "connectIfOpenedViaUri: opened via link") + if (chatModel.currentUser.value == null) { + chatModel.appOpenUrl.value = uri + } else { + withUriAction(uri) { linkType -> + val title = when (linkType) { + ConnectionLinkType.CONTACT -> generalGetString(MR.strings.connect_via_contact_link) + ConnectionLinkType.INVITATION -> generalGetString(MR.strings.connect_via_invitation_link) + ConnectionLinkType.GROUP -> generalGetString(MR.strings.connect_via_group_link) + } + AlertManager.shared.showAlertDialog( + title = title, + text = if (linkType == ConnectionLinkType.GROUP) + generalGetString(MR.strings.you_will_join_group) + else + generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link), + confirmText = generalGetString(MR.strings.connect_via_link_verb), + onConfirm = { + withApi { + Log.d(TAG, "connectIfOpenedViaUri: connecting") + connectViaUri(chatModel, linkType, uri) + } + } + ) + } + } +} + private var lazyListState = 0 to 0 @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt index 69b8f49d1a..e7782f4d43 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt @@ -3,7 +3,6 @@ package chat.simplex.app.views.database import SectionBottomSpacer import SectionTextFooter import SectionView -import android.content.Context import android.content.res.Configuration import android.net.Uri import android.util.Log @@ -21,8 +20,8 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.tooling.preview.Preview import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.ChatModel +import chat.simplex.app.platform.getFilesDirectory import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.* import chat.simplex.app.views.usersettings.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt index cf97b3c8c4..7dabbfde12 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt @@ -20,10 +20,11 @@ import androidx.compose.ui.unit.dp import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.AppPreferences +import chat.simplex.app.model.NotificationsMode +import chat.simplex.app.platform.initChatController import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.usersettings.AppVersionText -import chat.simplex.app.views.usersettings.NotificationsMode import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* @@ -199,7 +200,7 @@ private fun runChat( if (progressIndicator.value) return@launch progressIndicator.value = true try { - SimplexApp.context.initChatController(dbKey, confirmMigrations) + initChatController(dbKey, confirmMigrations) } catch (e: Exception) { Log.d(TAG, "initializeChat ${e.stackTraceToString()}") } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseView.kt index f82caf7e78..b01ccd9856 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseView.kt @@ -5,7 +5,6 @@ import SectionDividerSpaced import SectionTextFooter import SectionItemView import SectionView -import android.content.Context import android.content.res.Configuration import android.net.Uri import android.util.Log @@ -28,10 +27,9 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.fragment.app.FragmentActivity import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.usersettings.* @@ -357,7 +355,7 @@ private fun startChat(m: ChatModel, runChat: MutableState, chatLastSta withApi { try { if (chatDbChanged.value) { - SimplexApp.context.initChatController() + initChatController() chatDbChanged.value = false } if (m.chatDbStatus.value !is DBMigrationResult.OK) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt index 175d27c7c4..37bf8dd9dd 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import chat.simplex.app.R import chat.simplex.app.model.ChatInfo +import chat.simplex.app.platform.base64ToBitmap import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.common.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt rename to apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.common.kt index 71af738bda..7db325a110 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.common.kt @@ -8,10 +8,8 @@ import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import chat.simplex.app.ui.theme.* @Composable fun DefaultDropdownMenu( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt index c7c8154900..301ae120e7 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt @@ -29,86 +29,16 @@ import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.json -import chat.simplex.app.views.chat.PickFromGallery +import chat.simplex.app.platform.getAppFilesDirectory import chat.simplex.app.views.newchat.ActionButton import chat.simplex.res.MR import kotlinx.serialization.builtins.* -import java.io.ByteArrayOutputStream import java.io.File -import kotlin.math.min -import kotlin.math.sqrt - -// Inspired by https://github.com/MakeItEasyDev/Jetpack-Compose-Capture-Image-Or-Choose-from-Gallery -fun cropToSquare(image: Bitmap): Bitmap { - var xOffset = 0 - var yOffset = 0 - val side = min(image.height, image.width) - if (image.height < image.width) { - xOffset = (image.width - side) / 2 - } else { - yOffset = (image.height - side) / 2 - } - return Bitmap.createBitmap(image, xOffset, yOffset, side, side) -} - -fun resizeImageToStrSize(image: Bitmap, maxDataSize: Long): String { - var img = image - var str = compressImageStr(img) - while (str.length > maxDataSize) { - val ratio = sqrt(str.length.toDouble() / maxDataSize.toDouble()) - val clippedRatio = min(ratio, 2.0) - val width = (img.width.toDouble() / clippedRatio).toInt() - val height = img.height * width / img.width - img = Bitmap.createScaledBitmap(img, width, height, true) - str = compressImageStr(img) - } - return str -} - -private fun compressImageStr(bitmap: Bitmap): String { - val usePng = bitmap.hasAlpha() - val ext = if (usePng) "png" else "jpg" - return "data:image/$ext;base64," + Base64.encodeToString(compressImageData(bitmap, usePng).toByteArray(), Base64.NO_WRAP) -} - -fun resizeImageToDataSize(image: Bitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream { - var img = image - var stream = compressImageData(img, usePng) - while (stream.size() > maxDataSize) { - val ratio = sqrt(stream.size().toDouble() / maxDataSize.toDouble()) - val clippedRatio = min(ratio, 2.0) - val width = (img.width.toDouble() / clippedRatio).toInt() - val height = img.height * width / img.width - img = Bitmap.createScaledBitmap(img, width, height, true) - stream = compressImageData(img, usePng) - } - return stream -} - -private fun compressImageData(bitmap: Bitmap, usePng: Boolean): ByteArrayOutputStream { - val stream = ByteArrayOutputStream() - bitmap.compress(if (!usePng) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG, 85, stream) - return stream -} val errorBitmapBytes = Base64.decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg==", Base64.NO_WRAP) val errorBitmap: Bitmap = BitmapFactory.decodeByteArray(errorBitmapBytes, 0, errorBitmapBytes.size) -fun base64ToBitmap(base64ImageString: String): Bitmap { - val imageString = base64ImageString - .removePrefix("data:image/png;base64,") - .removePrefix("data:image/jpg;base64,") - try { - val imageBytes = Base64.decode(imageString, Base64.NO_WRAP) - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) - } catch (e: Exception) { - Log.e(TAG, "base64ToBitmap error: $e") - return errorBitmap - } -} - class CustomTakePicturePreview(var uri: Uri?, var tmpFile: File?): ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Void?): Intent { @@ -269,3 +199,65 @@ fun GetImageBottomSheet( } } } + +class PickFromGallery: ActivityResultContract() { + override fun createIntent(context: Context, input: Int) = + Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply { + type = "image/*" + } + + override fun parseResult(resultCode: Int, intent: Intent?): Uri? = intent?.data +} + +class PickMultipleImagesFromGallery: ActivityResultContract>() { + override fun createIntent(context: Context, input: Int) = + Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply { + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + type = "image/*" + } + + override fun parseResult(resultCode: Int, intent: Intent?): List = + if (intent?.data != null) + listOf(intent.data!!) + else if (intent?.clipData != null) + with(intent.clipData!!) { + val uris = ArrayList() + for (i in 0 until kotlin.math.min(itemCount, 10)) { + val uri = getItemAt(i).uri + if (uri != null) uris.add(uri) + } + if (itemCount > 10) { + AlertManager.shared.showAlertMsg(MR.strings.images_limit_title, MR.strings.images_limit_desc) + } + uris + } + else + emptyList() +} + + +class PickMultipleVideosFromGallery: ActivityResultContract>() { + override fun createIntent(context: Context, input: Int) = + Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI).apply { + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + type = "video/*" + } + + override fun parseResult(resultCode: Int, intent: Intent?): List = + if (intent?.data != null) + listOf(intent.data!!) + else if (intent?.clipData != null) + with(intent.clipData!!) { + val uris = ArrayList() + for (i in 0 until kotlin.math.min(itemCount, 10)) { + val uri = getItemAt(i).uri + if (uri != null) uris.add(uri) + } + if (itemCount > 10) { + AlertManager.shared.showAlertMsg(MR.strings.videos_limit_title, MR.strings.videos_limit_desc) + } + uris + } + else + emptyList() +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt index 47efef1da3..3f7135309f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.LinkPreview +import chat.simplex.app.platform.base64ToBitmap +import chat.simplex.app.platform.resizeImageToStrSize import chat.simplex.app.ui.theme.* import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt new file mode 100644 index 0000000000..3b1250a75e --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt @@ -0,0 +1,49 @@ +package chat.simplex.app.views.helpers + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.ui.Modifier +import chat.simplex.app.SimplexApp +import chat.simplex.app.views.localauth.LocalAuthView +import chat.simplex.res.MR + +sealed class LAResult { + object Success: LAResult() + class Error(val errString: CharSequence): LAResult() + class Failed(val errString: CharSequence? = null): LAResult() + class Unavailable(val errString: CharSequence? = null): LAResult() +} + +data class LocalAuthRequest ( + val title: String?, + val reason: String, + val password: String, + val selfDestruct: Boolean, + val completed: (LAResult) -> Unit +) { + companion object { + val sample = LocalAuthRequest(generalGetString(MR.strings.la_enter_app_passcode), generalGetString(MR.strings.la_authenticate), "", selfDestruct = false) { } + } +} + +fun authenticateWithPasscode( + promptTitle: String, + promptSubtitle: String, + selfDestruct: Boolean, + completed: (LAResult) -> Unit) { + val password = DatabaseUtils.ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password))) + ModalManager.shared.showPasscodeCustomModal { close -> + BackHandler { + close() + completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled))) + } + Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + LocalAuthView(SimplexApp.context.chatModel, LocalAuthRequest(promptTitle, promptSubtitle, password, selfDestruct && SimplexApp.context.chatModel.controller.appPrefs.selfDestruct.get()) { + close() + completed(it) + }) + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt index b6edae0e8f..6eed47b800 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt @@ -1,41 +1,14 @@ package chat.simplex.app.views.helpers import android.os.Build.VERSION.SDK_INT -import androidx.activity.compose.BackHandler import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager.Authenticators.* import androidx.biometric.BiometricPrompt -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity -import chat.simplex.app.R import chat.simplex.app.SimplexApp -import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword -import chat.simplex.app.views.localauth.LocalAuthView +import chat.simplex.app.platform.mainActivity import chat.simplex.app.views.usersettings.LAMode -import chat.simplex.res.MR - -sealed class LAResult { - object Success: LAResult() - class Error(val errString: CharSequence): LAResult() - class Failed(val errString: CharSequence? = null): LAResult() - class Unavailable(val errString: CharSequence? = null): LAResult() -} - -data class LocalAuthRequest ( - val title: String?, - val reason: String, - val password: String, - val selfDestruct: Boolean, - val completed: (LAResult) -> Unit -) { - companion object { - val sample = LocalAuthRequest(generalGetString(MR.strings.la_enter_app_passcode), generalGetString(MR.strings.la_authenticate), "", selfDestruct = false) { } - } -} fun authenticate( promptTitle: String, @@ -44,7 +17,7 @@ fun authenticate( usingLAMode: LAMode = SimplexApp.context.chatModel.controller.appPrefs.laMode.get(), completed: (LAResult) -> Unit ) { - val activity = SimplexApp.context.mainActivity.get() ?: return completed(LAResult.Error("")) + val activity = mainActivity.get() ?: return completed(LAResult.Error("")) when (usingLAMode) { LAMode.SYSTEM -> when { SDK_INT in 28..29 -> @@ -55,19 +28,7 @@ fun authenticate( else -> completed(LAResult.Unavailable()) } LAMode.PASSCODE -> { - val password = ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password))) - ModalManager.shared.showPasscodeCustomModal { close -> - BackHandler { - close() - completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled))) - } - Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - LocalAuthView(SimplexApp.context.chatModel, LocalAuthRequest(promptTitle, promptSubtitle, password, selfDestruct && SimplexApp.context.chatModel.controller.appPrefs.selfDestruct.get()) { - close() - completed(it) - }) - } - } + authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, completed) } } } @@ -119,30 +80,3 @@ private fun authenticateWithBiometricManager( else -> completed(LAResult.Unavailable()) } } - -fun laTurnedOnAlert() = AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.auth_simplex_lock_turned_on), - generalGetString(MR.strings.auth_you_will_be_required_to_authenticate_when_you_start_or_resume) -) - -fun laPasscodeNotSetAlert() = AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.lock_not_enabled), - generalGetString(MR.strings.you_can_turn_on_lock) -) - -fun laFailedAlert() { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.la_auth_failed), - text = generalGetString(MR.strings.la_could_not_be_verified) - ) -} - -fun laUnavailableInstructionAlert() = AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.auth_unavailable), - generalGetString(MR.strings.auth_device_authentication_is_not_enabled_you_can_turn_on_in_settings_once_enabled) -) - -fun laUnavailableTurningOffAlert() = AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.auth_unavailable), - generalGetString(MR.strings.auth_device_authentication_is_disabled_turning_off) -) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/MessagesFetcherWorker.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/MessagesFetcherWorker.kt index 5b18ff5220..6b43cd603a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/MessagesFetcherWorker.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/MessagesFetcherWorker.kt @@ -5,6 +5,7 @@ import android.util.Log import androidx.work.* import chat.simplex.app.* import chat.simplex.app.SimplexService.Companion.showPassphraseNotification +import chat.simplex.app.model.ChatController import kotlinx.coroutines.* import java.util.Date import java.util.concurrent.TimeUnit @@ -55,7 +56,7 @@ class MessagesFetcherWork( var shouldReschedule = true try { withTimeout(durationSeconds * 1000L) { - val chatController = (applicationContext as SimplexApp).chatController + val chatController = ChatController SimplexService.waitDbMigrationEnds(chatController) val chatDbStatus = chatController.chatModel.chatDbStatus.value if (chatDbStatus != DBMigrationResult.OK) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt index 9076c8ecdc..f0cdfaec8b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt @@ -18,8 +18,8 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.* import chat.simplex.app.TAG -import chat.simplex.app.chatParseMarkdown import chat.simplex.app.model.* +import chat.simplex.app.platform.chatParseMarkdown import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.item.MarkdownText import com.google.accompanist.insets.navigationBarsWithImePadding diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Util.kt index a61729a5cb..a36bf42de6 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -1,11 +1,7 @@ package chat.simplex.app.views.helpers -import android.app.Activity import android.app.Application //import android.app.LocaleManager -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.res.Configuration import android.content.res.Resources import android.graphics.* import android.graphics.Typeface @@ -17,15 +13,9 @@ import android.provider.OpenableColumns import android.text.Spanned import android.text.SpannedString import android.text.style.* -import android.util.Base64 import android.util.Log -import android.view.View -import android.view.ViewTreeObserver -import android.view.inputmethod.InputMethodManager import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.Saver import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.* import androidx.compose.ui.text.* import androidx.compose.ui.text.font.* @@ -33,77 +23,13 @@ import androidx.compose.ui.text.style.BaselineShift import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.* import androidx.core.content.FileProvider -import androidx.core.graphics.ColorUtils import androidx.core.text.HtmlCompat import chat.simplex.app.* import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.ThemeOverrides -import com.charleskorn.kaml.decodeFromStream +import chat.simplex.app.platform.getLoadedFilePath import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource -import kotlinx.coroutines.* -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import org.apache.commons.io.IOUtils import java.io.* -import java.text.SimpleDateFormat -import java.util.* -import kotlin.math.* - -fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action) - -fun withScope(scope: CoroutineScope, action: suspend CoroutineScope.() -> Unit): Job = - scope.launch { withContext(Dispatchers.Main, action) } - -fun withBGApi(action: suspend CoroutineScope.() -> Unit): Job = - CoroutineScope(Dispatchers.Default).launch(block = action) - -enum class KeyboardState { - Opened, Closed -} - -@Composable -fun getKeyboardState(): State { - val keyboardState = remember { mutableStateOf(KeyboardState.Closed) } - val view = LocalView.current - DisposableEffect(view) { - val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener { - val rect = Rect() - view.getWindowVisibleDisplayFrame(rect) - val screenHeight = view.rootView.height - val keypadHeight = screenHeight - rect.bottom - keyboardState.value = if (keypadHeight > screenHeight * 0.15) { - KeyboardState.Opened - } else { - KeyboardState.Closed - } - } - view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener) - - onDispose { - view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) - } - } - - return keyboardState -} - -fun hideKeyboard(view: View) = - (SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0) - -// Resource to annotated string from -// https://stackoverflow.com/questions/68549248/android-jetpack-compose-how-to-show-styled-text-from-string-resources -fun generalGetString(id: StringResource): String { - // prefer stringResource in Composable items to retain preview abilities - return id.getString(SimplexApp.context) -} - -@Composable -@ReadOnlyComposable -private fun resources(): Resources { - LocalConfiguration.current - return LocalContext.current.resources -} fun Spanned.toHtmlWithoutParagraphs(): String { return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) @@ -237,48 +163,6 @@ private fun spannableStringToAnnotatedString( } } -// maximum image file size to be auto-accepted -const val MAX_IMAGE_SIZE: Long = 261_120 // 255KB -const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2 -const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2 -const val MAX_VIDEO_SIZE_AUTO_RCV: Long = 1_047_552 // 1023KB - -const val MAX_VOICE_MILLIS_FOR_SENDING: Int = 300_000 - -const val MAX_FILE_SIZE_SMP: Long = 8000000 - -const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824 // 1GB - -fun getFilesDirectory(): String { - return SimplexApp.context.filesDir.toString() -} - -fun getTempFilesDirectory(): String { - return "${getFilesDirectory()}/temp_files" -} - -fun getAppFilesDirectory(): String { - return "${getFilesDirectory()}/app_files" -} - -fun getAppFilePath(fileName: String): String { - return "${getAppFilesDirectory()}/$fileName" -} - -fun getAppFileUri(fileName: String): Uri { - return Uri.parse("${getAppFilesDirectory()}/$fileName") -} - - -fun getLoadedFilePath(file: CIFile?): String? { - return if (file?.filePath != null && file.loaded) { - val filePath = getAppFilePath(file.filePath) - if (File(filePath).exists()) filePath else null - } else { - null - } -} - // https://developer.android.com/training/data-storage/shared/documents-files#bitmap fun getLoadedImage(file: CIFile?): Bitmap? { val filePath = getLoadedFilePath(file) @@ -343,7 +227,7 @@ fun getAppFilePath(uri: Uri): String? { return SimplexApp.context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.moveToFirst() - getAppFilePath(cursor.getString(nameIndex)) + chat.simplex.app.platform.getAppFilePath(cursor.getString(nameIndex)) } } @@ -395,69 +279,6 @@ fun getDrawableFromUri(uri: Uri, withAlertOnException: Boolean = true): Drawable } } -fun getThemeFromUri(uri: Uri, withAlertOnException: Boolean = true): ThemeOverrides? { - SimplexApp.context.contentResolver.openInputStream(uri).use { - runCatching { - return yaml.decodeFromStream(it!!) - }.onFailure { - if (withAlertOnException) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.import_theme_error), - text = generalGetString(MR.strings.import_theme_error_desc), - ) - } - } - } - return null -} - -fun saveImage(uri: Uri): String? { - val bitmap = getBitmapFromUri(uri) ?: return null - return saveImage(bitmap) -} - -fun saveImage(image: Bitmap): String? { - return try { - val ext = if (image.hasAlpha()) "png" else "jpg" - val dataResized = resizeImageToDataSize(image, ext == "png", maxDataSize = MAX_IMAGE_SIZE) - val fileToSave = generateNewFileName("IMG", ext) - val file = File(getAppFilePath(fileToSave)) - val output = FileOutputStream(file) - dataResized.writeTo(output) - output.flush() - output.close() - fileToSave - } catch (e: Exception) { - Log.e(chat.simplex.app.TAG, "Util.kt saveImage error: ${e.message}") - null - } -} - -fun saveAnimImage(uri: Uri): String? { - return try { - val filename = getFileName(uri)?.lowercase() - var ext = when { - // remove everything but extension - filename?.contains(".") == true -> filename.replaceBeforeLast('.', "").replace(".", "") - else -> "gif" - } - // Just in case the image has a strange extension - if (ext.length < 3 || ext.length > 4) ext = "gif" - val fileToSave = generateNewFileName("IMG", ext) - val file = File(getAppFilePath(fileToSave)) - val output = FileOutputStream(file) - SimplexApp.context.contentResolver.openInputStream(uri)!!.use { input -> - output.use { output -> - input.copyTo(output) - } - } - fileToSave - } catch (e: Exception) { - Log.e(chat.simplex.app.TAG, "Util.kt saveAnimImage error: ${e.message}") - null - } -} - fun saveTempImageUncompressed(image: Bitmap, asPng: Boolean): File? { return try { val ext = if (asPng) "png" else "jpg" @@ -476,103 +297,6 @@ fun saveTempImageUncompressed(image: Bitmap, asPng: Boolean): File? { } } -fun saveFileFromUri(uri: Uri): String? { - return try { - val inputStream = SimplexApp.context.contentResolver.openInputStream(uri) - val fileToSave = getFileName(uri) - if (inputStream != null && fileToSave != null) { - val destFileName = uniqueCombine(fileToSave) - val destFile = File(getAppFilePath(destFileName)) - IOUtils.copy(inputStream, FileOutputStream(destFile)) - destFileName - } else { - Log.e(chat.simplex.app.TAG, "Util.kt saveFileFromUri null inputStream") - null - } - } catch (e: Exception) { - Log.e(chat.simplex.app.TAG, "Util.kt saveFileFromUri error: ${e.message}") - null - } -} - -fun generateNewFileName(prefix: String, ext: String): String { - val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US) - sdf.timeZone = TimeZone.getTimeZone("GMT") - val timestamp = sdf.format(Date()) - return uniqueCombine("${prefix}_$timestamp.$ext") -} - -fun uniqueCombine(fileName: String): String { - val orig = File(fileName) - val name = orig.nameWithoutExtension - val ext = orig.extension - fun tryCombine(n: Int): String { - val suffix = if (n == 0) "" else "_$n" - val f = "$name$suffix.$ext" - return if (File(getAppFilePath(f)).exists()) tryCombine(n + 1) else f - } - return tryCombine(0) -} - -fun formatBytes(bytes: Long): String { - if (bytes == 0.toLong()) { - return "0 bytes" - } - val bytesDouble = bytes.toDouble() - val k = 1024.toDouble() - val units = arrayOf("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") - val i = floor(log2(bytesDouble) / log2(k)) - val size = bytesDouble / k.pow(i) - val unit = units[i.toInt()] - - return if (i <= 1) { - String.format("%.0f %s", size, unit) - } else { - String.format("%.2f %s", size, unit) - } -} - -fun removeFile(fileName: String): Boolean { - val file = File(getAppFilePath(fileName)) - val fileDeleted = file.delete() - if (!fileDeleted) { - Log.e(chat.simplex.app.TAG, "Util.kt removeFile error") - } - return fileDeleted -} - -fun deleteAppFiles() { - val dir = File(getAppFilesDirectory()) - try { - dir.list()?.forEach { - removeFile(it) - } - } catch (e: java.lang.Exception) { - Log.e(TAG, "Util deleteAppFiles error: ${e.stackTraceToString()}") - } -} - -fun directoryFileCountAndSize(dir: String): Pair { // count, size in bytes - var fileCount = 0 - var bytes = 0L - try { - File(dir).listFiles()?.forEach { - fileCount++ - bytes += it.length() - } - } catch (e: java.lang.Exception) { - Log.e(TAG, "Util directoryFileCountAndSize error: ${e.stackTraceToString()}") - } - return fileCount to bytes -} - -fun getMaxFileSize(fileProtocol: FileProtocol): Long { - return when (fileProtocol) { - FileProtocol.XFTP -> MAX_FILE_SIZE_XFTP - FileProtocol.SMP -> MAX_FILE_SIZE_SMP - } -} - fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true): VideoPlayer.PreviewAndDuration { val mmr = MediaMetadataRetriever() mmr.setDataSource(SimplexApp.context, uri) @@ -585,104 +309,3 @@ fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true mmr.release() return VideoPlayer.PreviewAndDuration(image, durationMs, timestamp ?: 0) } - -fun Color.darker(factor: Float = 0.1f): Color = - Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha) - -fun Color.lighter(factor: Float = 0.1f): Color = - Color(min(red * (1 + factor), 1f), min(green * (1 + factor), 1f), min(blue * (1 + factor), 1f), alpha) - -fun Color.mixWith(color: Color, alpha: Float): Color = - Color(ColorUtils.blendARGB(color.toArgb(), toArgb(), alpha)) - -fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT) - -fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT) - -val LongRange.Companion.saver - get() = Saver, Pair>( - save = { it.value.first to it.value.last }, - restore = { mutableStateOf(it.first..it.second) } - ) - -/* Make sure that T class has @Serializable annotation */ -inline fun serializableSaver(): Saver = Saver( - save = { json.encodeToString(it) }, - restore = { json.decodeFromString(it) } - ) - -fun saveAppLocale(pref: SharedPreference, activity: Activity, languageCode: String? = null) { -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { -// val localeManager = SimplexApp.context.getSystemService(LocaleManager::class.java) -// localeManager.applicationLocales = LocaleList(Locale.forLanguageTag(languageCode ?: return)) -// } else { - pref.set(languageCode) - if (languageCode == null) { - activity.applyLocale(SimplexApp.context.defaultLocale) - } - activity.recreate() -// } -} - -fun Activity.applyAppLocale(pref: SharedPreference) { -// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - val lang = pref.get() - if (lang == null || lang == Locale.getDefault().language) return - applyLocale(Locale.forLanguageTag(lang)) -// } -} - -private fun Activity.applyLocale(locale: Locale) { - Locale.setDefault(locale) - val appConf = Configuration(SimplexApp.context.resources.configuration).apply { setLocale(locale) } - val activityConf = Configuration(resources.configuration).apply { setLocale(locale) } - @Suppress("DEPRECATION") - SimplexApp.context.resources.updateConfiguration(appConf, resources.displayMetrics) - @Suppress("DEPRECATION") - resources.updateConfiguration(activityConf, resources.displayMetrics) -} - -fun UriHandler.openUriCatching(uri: String) { - try { - openUri(uri) - } catch (e: ActivityNotFoundException) { - Log.e(TAG, e.stackTraceToString()) - } -} - -fun IntSize.Companion.Saver(): Saver = Saver( - save = { it.width to it.height }, - restore = { IntSize(it.first, it.second) } -) - -@Composable -fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) { - val context = LocalContext.current - DisposableEffect(Unit) { - always() - val activity = context as? Activity ?: return@DisposableEffect onDispose {} - val orientation = activity.resources.configuration.orientation - onDispose { - whenDispose() - if (orientation == activity.resources.configuration.orientation) { - whenGone() - } - } - } -} - -@Composable -fun DisposableEffectOnRotate(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenRotate: () -> Unit) { - val context = LocalContext.current - DisposableEffect(Unit) { - always() - val activity = context as? Activity ?: return@DisposableEffect onDispose {} - val orientation = activity.resources.configuration.orientation - onDispose { - whenDispose() - if (orientation != activity.resources.configuration.orientation) { - whenRotate() - } - } - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt new file mode 100644 index 0000000000..1f04855cbd --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt @@ -0,0 +1,296 @@ +package chat.simplex.app.views.helpers + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.graphics.Bitmap +import android.net.Uri +import android.util.Base64 +import android.util.Log +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.Saver +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.UriHandler +import androidx.compose.ui.unit.IntSize +import androidx.core.graphics.ColorUtils +import chat.simplex.app.* +import chat.simplex.app.model.* +import chat.simplex.app.platform.getAppFilesDirectory +import chat.simplex.app.platform.resizeImageToDataSize +import chat.simplex.app.ui.theme.ThemeOverrides +import chat.simplex.res.MR +import com.charleskorn.kaml.decodeFromStream +import dev.icerock.moko.resources.StringResource +import kotlinx.coroutines.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import org.apache.commons.io.IOUtils +import java.io.* +import java.text.SimpleDateFormat +import java.util.* +import kotlin.math.* + +fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action) + +fun withScope(scope: CoroutineScope, action: suspend CoroutineScope.() -> Unit): Job = + scope.launch { withContext(Dispatchers.Main, action) } + +fun withBGApi(action: suspend CoroutineScope.() -> Unit): Job = + CoroutineScope(Dispatchers.Default).launch(block = action) + +enum class KeyboardState { + Opened, Closed +} + +fun generalGetString(id: StringResource): String { + // prefer stringResource in Composable items to retain preview abilities + return id.getString(SimplexApp.context) +} + +// maximum image file size to be auto-accepted +const val MAX_IMAGE_SIZE: Long = 261_120 // 255KB +const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2 +const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2 +const val MAX_VIDEO_SIZE_AUTO_RCV: Long = 1_047_552 // 1023KB + +const val MAX_VOICE_MILLIS_FOR_SENDING: Int = 300_000 + +const val MAX_FILE_SIZE_SMP: Long = 8000000 + +const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824 // 1GB + +fun getAppFileUri(fileName: String): Uri { + return Uri.parse("${getAppFilesDirectory()}/$fileName") +} + +fun getThemeFromUri(uri: Uri, withAlertOnException: Boolean = true): ThemeOverrides? { + SimplexApp.context.contentResolver.openInputStream(uri).use { + runCatching { + return yaml.decodeFromStream(it!!) + }.onFailure { + if (withAlertOnException) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.import_theme_error), + text = generalGetString(MR.strings.import_theme_error_desc), + ) + } + } + } + return null +} + +fun saveImage(uri: Uri): String? { + val bitmap = getBitmapFromUri(uri) ?: return null + return saveImage(bitmap) +} + +fun saveImage(image: Bitmap): String? { + return try { + val ext = if (image.hasAlpha()) "png" else "jpg" + val dataResized = resizeImageToDataSize(image, ext == "png", maxDataSize = MAX_IMAGE_SIZE) + val fileToSave = generateNewFileName("IMG", ext) + val file = File(chat.simplex.app.platform.getAppFilePath(fileToSave)) + val output = FileOutputStream(file) + dataResized.writeTo(output) + output.flush() + output.close() + fileToSave + } catch (e: Exception) { + Log.e(TAG, "Util.kt saveImage error: ${e.message}") + null + } +} + +fun saveAnimImage(uri: Uri): String? { + return try { + val filename = getFileName(uri)?.lowercase() + var ext = when { + // remove everything but extension + filename?.contains(".") == true -> filename.replaceBeforeLast('.', "").replace(".", "") + else -> "gif" + } + // Just in case the image has a strange extension + if (ext.length < 3 || ext.length > 4) ext = "gif" + val fileToSave = generateNewFileName("IMG", ext) + val file = File(chat.simplex.app.platform.getAppFilePath(fileToSave)) + val output = FileOutputStream(file) + SimplexApp.context.contentResolver.openInputStream(uri)!!.use { input -> + output.use { output -> + input.copyTo(output) + } + } + fileToSave + } catch (e: Exception) { + Log.e(TAG, "Util.kt saveAnimImage error: ${e.message}") + null + } +} + + +fun saveFileFromUri(uri: Uri): String? { + return try { + val inputStream = SimplexApp.context.contentResolver.openInputStream(uri) + val fileToSave = getFileName(uri) + if (inputStream != null && fileToSave != null) { + val destFileName = uniqueCombine(fileToSave) + val destFile = File(chat.simplex.app.platform.getAppFilePath(destFileName)) + IOUtils.copy(inputStream, FileOutputStream(destFile)) + destFileName + } else { + Log.e(TAG, "Util.kt saveFileFromUri null inputStream") + null + } + } catch (e: Exception) { + Log.e(TAG, "Util.kt saveFileFromUri error: ${e.message}") + null + } +} + +fun generateNewFileName(prefix: String, ext: String): String { + val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US) + sdf.timeZone = TimeZone.getTimeZone("GMT") + val timestamp = sdf.format(Date()) + return uniqueCombine("${prefix}_$timestamp.$ext") +} + +fun uniqueCombine(fileName: String): String { + val orig = File(fileName) + val name = orig.nameWithoutExtension + val ext = orig.extension + fun tryCombine(n: Int): String { + val suffix = if (n == 0) "" else "_$n" + val f = "$name$suffix.$ext" + return if (File(chat.simplex.app.platform.getAppFilePath(f)).exists()) tryCombine(n + 1) else f + } + return tryCombine(0) +} + +fun formatBytes(bytes: Long): String { + if (bytes == 0.toLong()) { + return "0 bytes" + } + val bytesDouble = bytes.toDouble() + val k = 1024.toDouble() + val units = arrayOf("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + val i = floor(log2(bytesDouble) / log2(k)) + val size = bytesDouble / k.pow(i) + val unit = units[i.toInt()] + + return if (i <= 1) { + String.format("%.0f %s", size, unit) + } else { + String.format("%.2f %s", size, unit) + } +} + +fun removeFile(fileName: String): Boolean { + val file = File(chat.simplex.app.platform.getAppFilePath(fileName)) + val fileDeleted = file.delete() + if (!fileDeleted) { + Log.e(TAG, "Util.kt removeFile error") + } + return fileDeleted +} + +fun deleteAppFiles() { + val dir = File(getAppFilesDirectory()) + try { + dir.list()?.forEach { + removeFile(it) + } + } catch (e: java.lang.Exception) { + Log.e(TAG, "Util deleteAppFiles error: ${e.stackTraceToString()}") + } +} + +fun directoryFileCountAndSize(dir: String): Pair { // count, size in bytes + var fileCount = 0 + var bytes = 0L + try { + File(dir).listFiles()?.forEach { + fileCount++ + bytes += it.length() + } + } catch (e: java.lang.Exception) { + Log.e(TAG, "Util directoryFileCountAndSize error: ${e.stackTraceToString()}") + } + return fileCount to bytes +} + +fun getMaxFileSize(fileProtocol: FileProtocol): Long { + return when (fileProtocol) { + FileProtocol.XFTP -> MAX_FILE_SIZE_XFTP + FileProtocol.SMP -> MAX_FILE_SIZE_SMP + } +} + +fun Color.darker(factor: Float = 0.1f): Color = + Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha) + +fun Color.lighter(factor: Float = 0.1f): Color = + Color(min(red * (1 + factor), 1f), min(green * (1 + factor), 1f), min(blue * (1 + factor), 1f), alpha) + +fun Color.mixWith(color: Color, alpha: Float): Color = + Color(ColorUtils.blendARGB(color.toArgb(), toArgb(), alpha)) + +fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT) + +fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT) + +val LongRange.Companion.saver + get() = Saver, Pair>( + save = { it.value.first to it.value.last }, + restore = { mutableStateOf(it.first..it.second) } + ) + +/* Make sure that T class has @Serializable annotation */ +inline fun serializableSaver(): Saver = Saver( + save = { json.encodeToString(it) }, + restore = { json.decodeFromString(it) } +) + +fun UriHandler.openUriCatching(uri: String) { + try { + openUri(uri) + } catch (e: ActivityNotFoundException) { + Log.e(TAG, e.stackTraceToString()) + } +} + +fun IntSize.Companion.Saver(): Saver = Saver( + save = { it.width to it.height }, + restore = { IntSize(it.first, it.second) } +) + +@Composable +fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) { + val context = LocalContext.current + DisposableEffect(Unit) { + always() + val activity = context as? Activity ?: return@DisposableEffect onDispose {} + val orientation = activity.resources.configuration.orientation + onDispose { + whenDispose() + if (orientation == activity.resources.configuration.orientation) { + whenGone() + } + } + } +} + +@Composable +fun DisposableEffectOnRotate(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenRotate: () -> Unit) { + val context = LocalContext.current + DisposableEffect(Unit) { + always() + val activity = context as? Activity ?: return@DisposableEffect onDispose {} + val orientation = activity.resources.configuration.orientation + onDispose { + whenDispose() + if (orientation != activity.resources.configuration.orientation) { + whenRotate() + } + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/LocalAuthView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/LocalAuthView.kt index 9187c85d9b..f3281b38e0 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/LocalAuthView.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import dev.icerock.moko.resources.compose.stringResource import chat.simplex.app.* import chat.simplex.app.model.* +import chat.simplex.app.platform.initChatController import chat.simplex.app.views.database.deleteChatAsync import chat.simplex.app.views.database.stopChatAsync import chat.simplex.app.views.helpers.* @@ -52,7 +53,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( m.chatDbChanged.value = true m.chatDbStatus.value = null try { - SimplexApp.context.initChatController(startChat = true) + initChatController(startChat = true) } catch (e: Exception) { Log.d(TAG, "initializeChat ${e.stackTraceToString()}") } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt index 39bd6cd333..fb987be6bb 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt @@ -16,7 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.usersettings.SettingsActionItem diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt index 2609098ebd..7b54b7ba2e 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.cropToSquare +import chat.simplex.app.platform.resizeImageToStrSize import chat.simplex.app.ui.theme.* import chat.simplex.app.views.ProfileNameField import chat.simplex.app.views.chat.group.AddGroupMembersView diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt index 3317472cc5..95501c2a88 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt @@ -14,8 +14,8 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.tooling.preview.Preview -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.LocalAliasEditor import chat.simplex.app.views.chatlist.deleteContactConnectionAlert diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt new file mode 100644 index 0000000000..0d48a68abe --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt @@ -0,0 +1,80 @@ +package chat.simplex.app.views.newchat + +import android.graphics.Bitmap +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.* +import androidx.compose.ui.platform.* +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.core.graphics.* +import boofcv.alg.drawing.FiducialImageEngine +import boofcv.alg.fiducial.qrcode.* +import boofcv.android.ConvertBitmap +import chat.simplex.app.platform.addLogo +import chat.simplex.app.platform.shareFile +import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import kotlinx.coroutines.launch + +@Composable +fun QRCode( + connReq: String, + modifier: Modifier = Modifier, + tintColor: Color = Color(0xff062d56), + withLogo: Boolean = true +) { + val scope = rememberCoroutineScope() + + BoxWithConstraints { + val maxWidthInPx = with(LocalDensity.current) { maxWidth.roundToPx() } + val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) { + qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo() else it } + .asImageBitmap() + } + Image( + bitmap = qr, + contentDescription = stringResource(MR.strings.image_descr_qr_code), + modifier + .clickable { + scope.launch { + val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo() else it } + val file = saveTempImageUncompressed(image, false) + if (file != null) { + shareFile("", file.absolutePath) + } + } + } + ) + } +} + +fun qrCodeBitmap(content: String, size: Int = 1024): Bitmap { + val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() + /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ + val numModules = QrCode.totalModules(qrCode.version) + val borderModule = 1 + // val calculatedFinalWidth = (pixelsPerModule * numModules) + 2 * (borderModule * pixelsPerModule) + // size = (x * numModules) + 2 * (borderModule * x) + // size / x = numModules + 2 * borderModule + // x = size / (numModules + 2 * borderModule) + val pixelsPerModule = size / (numModules + 2 * borderModule) + // + 1 to make it with better quality + val renderer = QrCodeGeneratorImage(pixelsPerModule + 1) + renderer.borderModule = borderModule + renderer.render(qrCode) + return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565).scale(size, size) +} + +@Preview +@Composable +fun PreviewQRCode() { + SimpleXTheme { + QRCode(connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt index c3830bd91b..7081acf028 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt @@ -1,76 +1,6 @@ package chat.simplex.app.views.newchat import android.graphics.Bitmap -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.* -import androidx.compose.ui.platform.* -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.core.graphics.* -import androidx.core.graphics.drawable.toBitmap -import boofcv.alg.drawing.FiducialImageEngine -import boofcv.alg.fiducial.qrcode.* -import boofcv.android.ConvertBitmap -import chat.simplex.app.R -import chat.simplex.app.SimplexApp -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.* -import chat.simplex.res.MR -import kotlinx.coroutines.launch - -@Composable -fun QRCode( - connReq: String, - modifier: Modifier = Modifier, - tintColor: Color = Color(0xff062d56), - withLogo: Boolean = true -) { - val scope = rememberCoroutineScope() - - BoxWithConstraints { - val maxWidthInPx = with(LocalDensity.current) { maxWidth.roundToPx() } - val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) { - qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } - .asImageBitmap() - } - Image( - bitmap = qr, - contentDescription = stringResource(MR.strings.image_descr_qr_code), - modifier - .clickable { - scope.launch { - val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } - val file = saveTempImageUncompressed(image, false) - if (file != null) { - shareFile("", file.absolutePath) - } - } - } - ) - } -} - -fun qrCodeBitmap(content: String, size: Int = 1024): Bitmap { - val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() - /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ - val numModules = QrCode.totalModules(qrCode.version) - val borderModule = 1 - // val calculatedFinalWidth = (pixelsPerModule * numModules) + 2 * (borderModule * pixelsPerModule) - // size = (x * numModules) + 2 * (borderModule * x) - // size / x = numModules + 2 * borderModule - // x = size / (numModules + 2 * borderModule) - val pixelsPerModule = size / (numModules + 2 * borderModule) - // + 1 to make it with better quality - val renderer = QrCodeGeneratorImage(pixelsPerModule + 1) - renderer.borderModule = borderModule - renderer.render(qrCode) - return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565).scale(size, size) -} fun Bitmap.replaceColor(from: Int, to: Int): Bitmap { val pixels = IntArray(width * height) @@ -85,22 +15,3 @@ fun Bitmap.replaceColor(from: Int, to: Int): Bitmap { setPixels(pixels, 0, width, 0, 0, width, height) return this } - -fun Bitmap.addLogo(): Bitmap = applyCanvas { - val radius = (width * 0.16f) / 2 - val paint = android.graphics.Paint() - paint.color = android.graphics.Color.WHITE - drawCircle(width / 2f, height / 2f, radius, paint) - val logo = SimplexApp.context.resources.getDrawable(R.mipmap.icon_foreground, null).toBitmap() - val logoSize = (width * 0.24).toInt() - translate((width - logoSize) / 2f, (height - logoSize) / 2f) - drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null) -} - -@Preview -@Composable -fun PreviewQRCode() { - SimpleXTheme { - QRCode(connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt new file mode 100644 index 0000000000..9c37a34207 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt @@ -0,0 +1,151 @@ +package chat.simplex.app.views.newchat + +import SectionBottomSpacer +import android.content.res.Configuration +import android.net.Uri +import android.util.Log +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.net.toUri +import chat.simplex.app.TAG +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.json +import chat.simplex.app.ui.theme.DEFAULT_PADDING +import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Composable +fun QRCodeScanner(close: () -> Unit) { + QRCodeScanner { connReqUri -> + try { + val uri = Uri.parse(connReqUri) + withUriAction(uri) { linkType -> + val action = suspend { + Log.d(TAG, "connectViaUri: connecting") + if (connectViaUri(ChatModel, linkType, uri)) { + close() + } + } + if (linkType == ConnectionLinkType.GROUP) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.connect_via_group_link), + text = generalGetString(MR.strings.you_will_join_group), + confirmText = generalGetString(MR.strings.connect_via_link_verb), + onConfirm = { withApi { action() } } + ) + } else action() + } + } catch (e: RuntimeException) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.invalid_QR_code), + text = generalGetString(MR.strings.this_QR_code_is_not_a_link) + ) + } + } +} + +enum class ConnectionLinkType { + CONTACT, INVITATION, GROUP +} + +@Serializable +sealed class CReqClientData { + @Serializable @SerialName("group") data class Group(val groupLinkId: String): CReqClientData() +} + +fun withUriAction(uri: Uri, run: suspend (ConnectionLinkType) -> Unit) { + val action = uri.path?.drop(1)?.replace("/", "") + val data = uri.toString().replaceFirst("#/", "/").toUri().getQueryParameter("data") + val type = when { + data != null -> { + val parsed = runCatching { + json.decodeFromString(CReqClientData.serializer(), data) + } + when { + parsed.getOrNull() is CReqClientData.Group -> ConnectionLinkType.GROUP + else -> null + } + } + action == "contact" -> ConnectionLinkType.CONTACT + action == "invitation" -> ConnectionLinkType.INVITATION + else -> null + } + if (type != null) { + withApi { run(type) } + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.invalid_contact_link), + text = generalGetString(MR.strings.this_link_is_not_a_valid_connection_link) + ) + } +} + +suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: Uri): Boolean { + val r = chatModel.controller.apiConnect(uri.toString()) + if (r) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.connection_request_sent), + text = + when (action) { + ConnectionLinkType.CONTACT -> generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted) + ConnectionLinkType.INVITATION -> generalGetString(MR.strings.you_will_be_connected_when_your_contacts_device_is_online) + ConnectionLinkType.GROUP -> generalGetString(MR.strings.you_will_be_connected_when_group_host_device_is_online) + } + ) + } + return r +} + +@Composable +fun ConnectContactLayout(chatModelIncognito: Boolean, close: () -> Unit) { + Column( + Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + AppBarTitle(stringResource(MR.strings.scan_QR_code), false) + InfoAboutIncognito( + chatModelIncognito, + true, + generalGetString(MR.strings.incognito_random_profile_description), + generalGetString(MR.strings.your_profile_will_be_sent) + ) + Box( + Modifier + .fillMaxWidth() + .aspectRatio(ratio = 1F) + .padding(bottom = 12.dp) + ) { QRCodeScanner(close) } + Text( + annotatedStringResource(MR.strings.if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link), + lineHeight = 22.sp + ) + SectionBottomSpacer() + } +} + +@Preview +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewConnectContactLayout() { + SimpleXTheme { + ConnectContactLayout( + chatModelIncognito = false, + close = {} + ) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt index 2bbef58cf8..dd159a4c91 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt @@ -1,33 +1,10 @@ package chat.simplex.app.views.newchat -import SectionBottomSpacer import android.Manifest -import android.content.res.Configuration -import android.net.Uri -import android.util.Log -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.net.toUri -import chat.simplex.app.R -import chat.simplex.app.TAG import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.json -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.* import com.google.accompanist.permissions.rememberPermissionState -import chat.simplex.res.MR -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable @Composable fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { @@ -37,128 +14,6 @@ fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { } ConnectContactLayout( chatModelIncognito = chatModel.incognito.value, - qrCodeScanner = { - QRCodeScanner { connReqUri -> - try { - val uri = Uri.parse(connReqUri) - withUriAction(uri) { linkType -> - val action = suspend { - Log.d(TAG, "connectViaUri: connecting") - if (connectViaUri(chatModel, linkType, uri)) { - close() - } - } - if (linkType == ConnectionLinkType.GROUP) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.connect_via_group_link), - text = generalGetString(MR.strings.you_will_join_group), - confirmText = generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { action() } } - ) - } else action() - } - } catch (e: RuntimeException) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.invalid_QR_code), - text = generalGetString(MR.strings.this_QR_code_is_not_a_link) - ) - } - } - }, + close ) } - -enum class ConnectionLinkType { - CONTACT, INVITATION, GROUP -} - -@Serializable -sealed class CReqClientData { - @Serializable @SerialName("group") data class Group(val groupLinkId: String): CReqClientData() -} - -fun withUriAction(uri: Uri, run: suspend (ConnectionLinkType) -> Unit) { - val action = uri.path?.drop(1)?.replace("/", "") - val data = uri.toString().replaceFirst("#/", "/").toUri().getQueryParameter("data") - val type = when { - data != null -> { - val parsed = runCatching { - json.decodeFromString(CReqClientData.serializer(), data) - } - when { - parsed.getOrNull() is CReqClientData.Group -> ConnectionLinkType.GROUP - else -> null - } - } - action == "contact" -> ConnectionLinkType.CONTACT - action == "invitation" -> ConnectionLinkType.INVITATION - else -> null - } - if (type != null) { - withApi { run(type) } - } else { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.invalid_contact_link), - text = generalGetString(MR.strings.this_link_is_not_a_valid_connection_link) - ) - } -} - -suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: Uri): Boolean { - val r = chatModel.controller.apiConnect(uri.toString()) - if (r) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.connection_request_sent), - text = - when (action) { - ConnectionLinkType.CONTACT -> generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted) - ConnectionLinkType.INVITATION -> generalGetString(MR.strings.you_will_be_connected_when_your_contacts_device_is_online) - ConnectionLinkType.GROUP -> generalGetString(MR.strings.you_will_be_connected_when_group_host_device_is_online) - } - ) - } - return r -} - -@Composable -fun ConnectContactLayout(chatModelIncognito: Boolean, qrCodeScanner: @Composable () -> Unit) { - Column( - Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - AppBarTitle(stringResource(MR.strings.scan_QR_code), false) - InfoAboutIncognito( - chatModelIncognito, - true, - generalGetString(MR.strings.incognito_random_profile_description), - generalGetString(MR.strings.your_profile_will_be_sent) - ) - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - .padding(bottom = 12.dp) - ) { qrCodeScanner() } - Text( - annotatedStringResource(MR.strings.if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link), - lineHeight = 22.sp - ) - SectionBottomSpacer() - } -} - -@Preview -@Preview( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -) -@Composable -fun PreviewConnectContactLayout() { - SimpleXTheme { - ConnectContactLayout( - chatModelIncognito = false, - qrCodeScanner = { Surface {} }, - ) - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt index af8b93c0f3..6c632160dc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt @@ -14,9 +14,10 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import chat.simplex.app.* -import chat.simplex.app.R import chat.simplex.app.model.ChatModel import chat.simplex.app.model.UserContactLinkRec +import chat.simplex.app.platform.sendEmail +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.QRCode diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt index 997d9edb4b..ac37eff8d8 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt @@ -7,7 +7,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import chat.simplex.app.model.ChatModel import chat.simplex.app.views.CreateProfilePanel -import chat.simplex.app.views.helpers.getKeyboardState +import chat.simplex.app.platform.getKeyboardState import com.google.accompanist.insets.ProvideWindowInsets import kotlinx.coroutines.launch diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt new file mode 100644 index 0000000000..b76629952f --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt @@ -0,0 +1,78 @@ +package chat.simplex.app.views.onboarding + +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 +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.usersettings.changeNotificationsMode +import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource + +@Composable +fun SetNotificationsMode(m: ChatModel) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(vertical = 14.dp) + ) { + //CloseSheetBar(null) + AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title)) + val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } + Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { + Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING * 2f)) + NotificationButton(currentMode, NotificationsMode.OFF, MR.strings.onboarding_notifications_mode_off, MR.strings.onboarding_notifications_mode_off_desc) + NotificationButton(currentMode, NotificationsMode.PERIODIC, MR.strings.onboarding_notifications_mode_periodic, MR.strings.onboarding_notifications_mode_periodic_desc) + NotificationButton(currentMode, NotificationsMode.SERVICE, MR.strings.onboarding_notifications_mode_service, MR.strings.onboarding_notifications_mode_service_desc) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), contentAlignment = Alignment.Center) { + OnboardingActionButton(MR.strings.use_chat, OnboardingStage.OnboardingComplete, m.onboardingStage, false) { + changeNotificationsMode(currentMode.value, m) + } + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + } + SetNotificationsModeAdditions() +} + +@Composable +private fun NotificationButton(currentMode: MutableState, mode: NotificationsMode, title: StringResource, description: StringResource) { + TextButton( + onClick = { currentMode.value = mode }, + border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), + shape = RoundedCornerShape(35.dp), + ) { + Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp)) { + Text( + stringResource(title), + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium, + color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally), + textAlign = TextAlign.Center + ) + Text(annotatedStringResource(description), + Modifier.align(Alignment.CenterHorizontally), + fontSize = 15.sp, + color = MaterialTheme.colors.onBackground, + lineHeight = 24.sp, + textAlign = TextAlign.Center + ) + } + } + Spacer(Modifier.height(14.dp)) +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt index 9f3b87194e..0d8ac2ef1c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt @@ -2,59 +2,10 @@ package chat.simplex.app.views.onboarding import android.Manifest import android.os.Build -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 -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import chat.simplex.app.R +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import chat.simplex.app.SimplexApp -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.NtfManager -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.NotificationsMode -import chat.simplex.app.views.usersettings.changeNotificationsMode import com.google.accompanist.permissions.rememberPermissionState -import chat.simplex.res.MR -import dev.icerock.moko.resources.StringResource - -@Composable -fun SetNotificationsMode(m: ChatModel) { - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(vertical = 14.dp) - ) { - //CloseSheetBar(null) - AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title)) - val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } - Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { - Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) - Spacer(Modifier.height(DEFAULT_PADDING * 2f)) - NotificationButton(currentMode, NotificationsMode.OFF, MR.strings.onboarding_notifications_mode_off, MR.strings.onboarding_notifications_mode_off_desc) - NotificationButton(currentMode, NotificationsMode.PERIODIC, MR.strings.onboarding_notifications_mode_periodic, MR.strings.onboarding_notifications_mode_periodic_desc) - NotificationButton(currentMode, NotificationsMode.SERVICE, MR.strings.onboarding_notifications_mode_service, MR.strings.onboarding_notifications_mode_service_desc) - } - Spacer(Modifier.fillMaxHeight().weight(1f)) - Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), contentAlignment = Alignment.Center) { - OnboardingActionButton(MR.strings.use_chat, OnboardingStage.OnboardingComplete, m.onboardingStage, false) { - changeNotificationsMode(currentMode.value, m) - } - } - Spacer(Modifier.fillMaxHeight().weight(1f)) - } - SetNotificationsModeAdditions() -} @Composable fun SetNotificationsModeAdditions() { @@ -73,31 +24,3 @@ fun SetNotificationsModeAdditions() { } } } - -@Composable -private fun NotificationButton(currentMode: MutableState, mode: NotificationsMode, title: StringResource, description: StringResource) { - TextButton( - onClick = { currentMode.value = mode }, - border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), - shape = RoundedCornerShape(35.dp), - ) { - Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp)) { - Text( - stringResource(title), - style = MaterialTheme.typography.h3, - fontWeight = FontWeight.Medium, - color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally), - textAlign = TextAlign.Center - ) - Text(annotatedStringResource(description), - Modifier.align(Alignment.CenterHorizontally), - fontSize = 15.sp, - color = MaterialTheme.colors.onBackground, - lineHeight = 24.sp, - textAlign = TextAlign.Center - ) - } - } - Spacer(Modifier.height(14.dp)) -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt new file mode 100644 index 0000000000..5fe8ddc318 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt @@ -0,0 +1,288 @@ +package chat.simplex.app.views.usersettings + +import SectionBottomSpacer +import SectionItemView +import SectionItemViewSpaceBetween +import SectionSpacer +import SectionView +import android.net.Uri +import android.util.Log +import android.widget.Toast +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import chat.simplex.app.SimplexApp +import chat.simplex.app.TAG +import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.helpers.* +import chat.simplex.res.MR +import com.godaddy.android.colorpicker.ClassicColorPicker +import com.godaddy.android.colorpicker.HsvColor +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.serialization.encodeToString +import java.io.BufferedOutputStream +import java.util.* +import kotlin.collections.ArrayList + +object AppearanceScope { + @Composable + fun ThemesSection( + systemDarkTheme: SharedPreference, + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + editColor: (ThemeColor, Color) -> Unit + ) { + val currentTheme by CurrentColors.collectAsState() + SectionView(stringResource(MR.strings.settings_section_title_themes)) { + val darkTheme = isSystemInDarkTheme() + val state = remember { derivedStateOf { currentTheme.name } } + ThemeSelector(state) { + ThemeManager.applyTheme(it, darkTheme) + } + if (state.value == DefaultTheme.SYSTEM.name) { + DarkThemeSelector(remember { systemDarkTheme.state }) { + ThemeManager.changeDarkTheme(it, darkTheme) + } + } + } + SectionItemView(showSettingsModal { _ -> CustomizeThemeView(editColor) }) { Text(stringResource(MR.strings.customize_theme_title)) } + } + + @Composable + fun CustomizeThemeView(editColor: (ThemeColor, Color) -> Unit) { + Column( + Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ) { + val currentTheme by CurrentColors.collectAsState() + + AppBarTitle(stringResource(MR.strings.customize_theme_title)) + + SectionView(stringResource(MR.strings.theme_colors_section_title)) { + SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) { + val title = generalGetString(MR.strings.color_primary) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.primary) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT, currentTheme.colors.primaryVariant) }) { + val title = generalGetString(MR.strings.color_primary_variant) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.primaryVariant) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY, currentTheme.colors.secondary) }) { + val title = generalGetString(MR.strings.color_secondary) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.secondary) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT, currentTheme.colors.secondaryVariant) }) { + val title = generalGetString(MR.strings.color_secondary_variant) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.secondaryVariant) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND, currentTheme.colors.background) }) { + val title = generalGetString(MR.strings.color_background) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.background) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE, currentTheme.colors.surface) }) { + val title = generalGetString(MR.strings.color_surface) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.surface) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE, currentTheme.appColors.title) }) { + val title = generalGetString(MR.strings.color_title) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.title) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE, currentTheme.appColors.sentMessage) }) { + val title = generalGetString(MR.strings.color_sent_message) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.sentMessage) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_MESSAGE, currentTheme.appColors.receivedMessage) }) { + val title = generalGetString(MR.strings.color_received_message) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.receivedMessage) + } + } + val isInDarkTheme = isInDarkTheme() + if (currentTheme.base.hasChangedAnyColor(currentTheme.colors, currentTheme.appColors)) { + SectionItemView({ ThemeManager.resetAllThemeColors(darkForSystemTheme = isInDarkTheme) }) { + Text(generalGetString(MR.strings.reset_color), color = MaterialTheme.colors.primary) + } + } + SectionSpacer() + SectionView { + val theme = remember { mutableStateOf(null as String?) } + val exportThemeLauncher = rememberSaveThemeLauncher(theme) + SectionItemView({ + val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme) + theme.value = yaml.encodeToString(overrides) + exportThemeLauncher.launch("simplex.theme") + }) { + Text(generalGetString(MR.strings.export_theme), color = MaterialTheme.colors.primary) + } + + val importThemeLauncher = rememberGetContentLauncher { uri: Uri? -> + if (uri != null) { + val theme = getThemeFromUri(uri) + if (theme != null) { + ThemeManager.saveAndApplyThemeOverrides(theme, isInDarkTheme) + } + } + } + // Can not limit to YAML mime type since it's unsupported by Android + SectionItemView({ importThemeLauncher.launch("*/*") }) { + Text(generalGetString(MR.strings.import_theme), color = MaterialTheme.colors.primary) + } + } + SectionBottomSpacer() + } + } + + @Composable + fun ColorEditor( + name: ThemeColor, + initialColor: Color, + close: () -> Unit, + ) { + Column( + Modifier + .fillMaxWidth() + ) { + AppBarTitle(name.text) + var currentColor by remember { mutableStateOf(initialColor) } + ColorPicker(initialColor) { + currentColor = it + } + + SectionSpacer() + val isInDarkTheme = isInDarkTheme() + TextButton( + onClick = { + ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme) + close() + }, + Modifier.align(Alignment.CenterHorizontally), + colors = ButtonDefaults.textButtonColors(contentColor = currentColor) + ) { + Text(generalGetString(MR.strings.save_color)) + } + } + } + + @Composable + fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { + ClassicColorPicker( + color = initialColor, + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + showAlphaBar = true, + onColorChanged = { color: HsvColor -> + onColorChanged(color.toColor()) + } + ) + } + + @Composable + fun LangSelector(state: State, onSelected: (String) -> Unit) { + // Should be the same as in app/build.gradle's `android.defaultConfig.resConfigs` + val supportedLanguages = mapOf( + "system" to generalGetString(MR.strings.language_system), + "en" to "English", + "cs" to "Čeština", + "de" to "Deutsch", + "es" to "Español", + "fr" to "Français", + "it" to "Italiano", + "ja" to "日本語", + "nl" to "Nederlands", + "pl" to "Polski", + "pt-BR" to "Português (Brasil)", + "ru" to "Русский", + "zh-CN" to "简体中文" + ) + val values by remember { mutableStateOf(supportedLanguages.map { it.key to it.value }) } + ExposedDropDownSettingRow( + generalGetString(MR.strings.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() }, + values, + state, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = onSelected + ) + } + + @Composable + private fun ThemeSelector(state: State, onSelected: (String) -> Unit) { + val darkTheme = isSystemInDarkTheme() + val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third }) } + ExposedDropDownSettingRow( + generalGetString(MR.strings.theme), + values, + state, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = onSelected + ) + } + + @Composable + private fun DarkThemeSelector(state: State, onSelected: (String) -> Unit) { + val values by remember { + val darkThemes = ArrayList>() + darkThemes.add(DefaultTheme.DARK.name to generalGetString(MR.strings.theme_dark)) + darkThemes.add(DefaultTheme.SIMPLEX.name to generalGetString(MR.strings.theme_simplex)) + mutableStateOf(darkThemes.toList()) + } + ExposedDropDownSettingRow( + generalGetString(MR.strings.dark_theme), + values, + state, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = { if (it != null) onSelected(it) } + ) + } + + //private fun openSystemLangPicker(activity: Activity) { + // activity.startActivity(Intent(Settings.ACTION_APP_LOCALE_SETTINGS, Uri.parse("package:" + SimplexApp.context.packageName))) + //} + + @Composable + private fun rememberSaveThemeLauncher(theme: MutableState): ManagedActivityResultLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.CreateDocument(), + onResult = { destination -> + val cxt = SimplexApp.context + try { + destination?.let { + val theme = theme.value + if (theme != null) { + val contentResolver = cxt.contentResolver + contentResolver.openOutputStream(destination)?.let { stream -> + BufferedOutputStream(stream).use { outputStream -> + theme.byteInputStream().use { it.copyTo(outputStream) } + } + Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() + } + } + } + } catch (e: Error) { + Toast.makeText(cxt, generalGetString(MR.strings.error_saving_file), Toast.LENGTH_SHORT).show() + Log.e(TAG, "rememberSaveThemeLauncher error saving theme $e") + } finally { + theme.value = null + } + } + ) +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt index cdf1f9900a..fba7ecc8ad 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt @@ -39,9 +39,13 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap import chat.simplex.app.* import chat.simplex.app.R +import chat.simplex.app.helpers.saveAppLocale import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.usersettings.AppearanceScope.ColorEditor +import chat.simplex.app.views.usersettings.AppearanceScope.LangSelector +import chat.simplex.app.views.usersettings.AppearanceScope.ThemesSection import com.godaddy.android.colorpicker.* import chat.simplex.res.MR import kotlinx.coroutines.delay @@ -155,253 +159,11 @@ fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> } SectionDividerSpaced(maxTopPadding = true) - val currentTheme by CurrentColors.collectAsState() - SectionView(stringResource(MR.strings.settings_section_title_themes)) { - val darkTheme = isSystemInDarkTheme() - val state = remember { derivedStateOf { currentTheme.name } } - ThemeSelector(state) { - ThemeManager.applyTheme(it, darkTheme) - } - if (state.value == DefaultTheme.SYSTEM.name) { - DarkThemeSelector(remember { systemDarkTheme.state }) { - ThemeManager.changeDarkTheme(it, darkTheme) - } - } - } - SectionItemView(showSettingsModal { _ -> CustomizeThemeView(editColor) }) { Text(stringResource(MR.strings.customize_theme_title)) } + ThemesSection(systemDarkTheme, showSettingsModal, editColor) SectionBottomSpacer() } } -@Composable -fun CustomizeThemeView(editColor: (ThemeColor, Color) -> Unit) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - ) { - val currentTheme by CurrentColors.collectAsState() - - AppBarTitle(stringResource(MR.strings.customize_theme_title)) - - SectionView(stringResource(MR.strings.theme_colors_section_title)) { - SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) { - val title = generalGetString(MR.strings.color_primary) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primary) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT, currentTheme.colors.primaryVariant) }) { - val title = generalGetString(MR.strings.color_primary_variant) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primaryVariant) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY, currentTheme.colors.secondary) }) { - val title = generalGetString(MR.strings.color_secondary) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondary) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT, currentTheme.colors.secondaryVariant) }) { - val title = generalGetString(MR.strings.color_secondary_variant) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondaryVariant) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND, currentTheme.colors.background) }) { - val title = generalGetString(MR.strings.color_background) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.background) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE, currentTheme.colors.surface) }) { - val title = generalGetString(MR.strings.color_surface) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.surface) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE, currentTheme.appColors.title) }) { - val title = generalGetString(MR.strings.color_title) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.title) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE, currentTheme.appColors.sentMessage) }) { - val title = generalGetString(MR.strings.color_sent_message) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.sentMessage) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_MESSAGE, currentTheme.appColors.receivedMessage) }) { - val title = generalGetString(MR.strings.color_received_message) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.receivedMessage) - } - } - val isInDarkTheme = isInDarkTheme() - if (currentTheme.base.hasChangedAnyColor(currentTheme.colors, currentTheme.appColors)) { - SectionItemView({ ThemeManager.resetAllThemeColors(darkForSystemTheme = isInDarkTheme) }) { - Text(generalGetString(MR.strings.reset_color), color = colors.primary) - } - } - SectionSpacer() - SectionView { - val theme = remember { mutableStateOf(null as String?) } - val exportThemeLauncher = rememberSaveThemeLauncher(theme) - SectionItemView({ - val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme) - theme.value = yaml.encodeToString(overrides) - exportThemeLauncher.launch("simplex.theme") - }) { - Text(generalGetString(MR.strings.export_theme), color = colors.primary) - } - - val importThemeLauncher = rememberGetContentLauncher { uri: Uri? -> - if (uri != null) { - val theme = getThemeFromUri(uri) - if (theme != null) { - ThemeManager.saveAndApplyThemeOverrides(theme, isInDarkTheme) - } - } - } - // Can not limit to YAML mime type since it's unsupported by Android - SectionItemView({ importThemeLauncher.launch("*/*") }) { - Text(generalGetString(MR.strings.import_theme), color = colors.primary) - } - } - SectionBottomSpacer() - } -} - -@Composable -fun ColorEditor( - name: ThemeColor, - initialColor: Color, - close: () -> Unit, -) { - Column( - Modifier - .fillMaxWidth() - ) { - AppBarTitle(name.text) - var currentColor by remember { mutableStateOf(initialColor) } - ColorPicker(initialColor) { - currentColor = it - } - - SectionSpacer() - val isInDarkTheme = isInDarkTheme() - TextButton( - onClick = { - ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme) - close() - }, - Modifier.align(Alignment.CenterHorizontally), - colors = ButtonDefaults.textButtonColors(contentColor = currentColor) - ) { - Text(generalGetString(MR.strings.save_color)) - } - } -} - -@Composable -fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { - ClassicColorPicker( - color = initialColor, - modifier = Modifier - .fillMaxWidth() - .height(300.dp), - showAlphaBar = true, - onColorChanged = { color: HsvColor -> - onColorChanged(color.toColor()) - } - ) -} - -@Composable -private fun LangSelector(state: State, onSelected: (String) -> Unit) { - // Should be the same as in app/build.gradle's `android.defaultConfig.resConfigs` - val supportedLanguages = mapOf( - "system" to generalGetString(MR.strings.language_system), - "en" to "English", - "cs" to "Čeština", - "de" to "Deutsch", - "es" to "Español", - "fr" to "Français", - "it" to "Italiano", - "ja" to "日本語", - "nl" to "Nederlands", - "pl" to "Polski", - "pt-BR" to "Português (Brasil)", - "ru" to "Русский", - "zh-CN" to "简体中文" - ) - val values by remember { mutableStateOf(supportedLanguages.map { it.key to it.value }) } - ExposedDropDownSettingRow( - generalGetString(MR.strings.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() }, - values, - state, - icon = null, - enabled = remember { mutableStateOf(true) }, - onSelected = onSelected - ) -} - -@Composable -private fun ThemeSelector(state: State, onSelected: (String) -> Unit) { - val darkTheme = isSystemInDarkTheme() - val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third }) } - ExposedDropDownSettingRow( - generalGetString(MR.strings.theme), - values, - state, - icon = null, - enabled = remember { mutableStateOf(true) }, - onSelected = onSelected - ) -} - -@Composable -private fun DarkThemeSelector(state: State, onSelected: (String) -> Unit) { - val values by remember { - val darkThemes = ArrayList>() - darkThemes.add(DefaultTheme.DARK.name to generalGetString(MR.strings.theme_dark)) - darkThemes.add(DefaultTheme.SIMPLEX.name to generalGetString(MR.strings.theme_simplex)) - mutableStateOf(darkThemes.toList()) - } - ExposedDropDownSettingRow( - generalGetString(MR.strings.dark_theme), - values, - state, - icon = null, - enabled = remember { mutableStateOf(true) }, - onSelected = { if (it != null) onSelected(it) } - ) -} - -//private fun openSystemLangPicker(activity: Activity) { -// activity.startActivity(Intent(Settings.ACTION_APP_LOCALE_SETTINGS, Uri.parse("package:" + SimplexApp.context.packageName))) -//} - -@Composable -private fun rememberSaveThemeLauncher(theme: MutableState): ManagedActivityResultLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument(), - onResult = { destination -> - val cxt = SimplexApp.context - try { - destination?.let { - val theme = theme.value - if (theme != null) { - val contentResolver = cxt.contentResolver - contentResolver.openOutputStream(destination)?.let { stream -> - BufferedOutputStream(stream).use { outputStream -> - theme.byteInputStream().use { it.copyTo(outputStream) } - } - Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() - } - } - } - } catch (e: Error) { - Toast.makeText(cxt, generalGetString(MR.strings.error_saving_file), Toast.LENGTH_SHORT).show() - Log.e(TAG, "rememberSaveThemeLauncher error saving theme $e") - } finally { - theme.value = null - } - } - ) - private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon -> SimplexApp.context.packageManager.getComponentEnabledSetting( ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}") diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt index 054686b0de..af58e98dff 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt @@ -16,23 +16,13 @@ import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import chat.simplex.app.* import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.NotificationsMode import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.* import kotlin.collections.ArrayList -enum class NotificationsMode(private val requiresIgnoringBatterySinceSdk: Int) { - OFF(Int.MAX_VALUE), PERIODIC(Build.VERSION_CODES.M), SERVICE(Build.VERSION_CODES.S), /*INSTANT(Int.MAX_VALUE) - for Firebase notifications */; - - val requiresIgnoringBattery - get() = requiresIgnoringBatterySinceSdk <= Build.VERSION.SDK_INT - - companion object { - val default: NotificationsMode = SERVICE - } -} - enum class NotificationPreviewMode { MESSAGE, CONTACT, HIDDEN; diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt new file mode 100644 index 0000000000..3ec12a00e8 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt @@ -0,0 +1,484 @@ +package chat.simplex.app.views.usersettings + +import SectionBottomSpacer +import SectionDividerSpaced +import SectionItemView +import SectionTextFooter +import SectionView +import android.view.WindowManager +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.fragment.app.FragmentActivity +import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.ProfileNameField +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword +import chat.simplex.app.views.helpers.DatabaseUtils.ksSelfDestructPassword +import chat.simplex.app.views.isValidDisplayName +import chat.simplex.app.views.localauth.SetAppPasscodeView +import chat.simplex.app.views.onboarding.ReadableText +import chat.simplex.res.MR + +enum class LAMode { + SYSTEM, + PASSCODE; + + val text: String + get() = when (this) { + SYSTEM -> generalGetString(MR.strings.la_mode_system) + PASSCODE -> generalGetString(MR.strings.la_mode_passcode) + } +} + +@Composable +fun PrivacySettingsView( + chatModel: ChatModel, + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + setPerformLA: (Boolean) -> Unit +) { + Column( + Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ) { + val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode + AppBarTitle(stringResource(MR.strings.your_privacy)) + PrivacyDeviceSection(showSettingsModal, setPerformLA) + SectionDividerSpaced() + + SectionView(stringResource(MR.strings.settings_section_title_chats)) { + SettingsPreferenceItem(painterResource(MR.images.ic_image), stringResource(MR.strings.auto_accept_images), chatModel.controller.appPrefs.privacyAcceptImages) + SettingsPreferenceItem(painterResource(MR.images.ic_travel_explore), stringResource(MR.strings.send_link_previews), chatModel.controller.appPrefs.privacyLinkPreviews) + SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = { + simplexLinkMode.set(it) + chatModel.simplexLinkMode.value = it + }) + } + if (chatModel.simplexLinkMode.value == SimplexLinkMode.BROWSER) { + SectionTextFooter(stringResource(MR.strings.simplex_link_mode_browser_warning)) + } + SectionBottomSpacer() + } +} + +@Composable +private fun SimpleXLinkOptions(simplexLinkModeState: State, onSelected: (SimplexLinkMode) -> Unit) { + val values = remember { + SimplexLinkMode.values().map { + when (it) { + SimplexLinkMode.DESCRIPTION -> it to generalGetString(MR.strings.simplex_link_mode_description) + SimplexLinkMode.FULL -> it to generalGetString(MR.strings.simplex_link_mode_full) + SimplexLinkMode.BROWSER -> it to generalGetString(MR.strings.simplex_link_mode_browser) + } + } + } + ExposedDropDownSettingRow( + generalGetString(MR.strings.simplex_link_mode), + values, + simplexLinkModeState, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = onSelected + ) +} + +private val laDelays = listOf(10, 30, 60, 180, 0) + +@Composable +fun SimplexLockView( + chatModel: ChatModel, + currentLAMode: SharedPreference, + setPerformLA: (Boolean) -> Unit +) { + val performLA = remember { chatModel.performLA } + val laMode = remember { chatModel.controller.appPrefs.laMode.state } + val laLockDelay = remember { chatModel.controller.appPrefs.laLockDelay } + val showChangePasscode = remember { derivedStateOf { performLA.value && currentLAMode.state.value == LAMode.PASSCODE } } + val activity = LocalContext.current as FragmentActivity + val selfDestructPref = remember { chatModel.controller.appPrefs.selfDestruct } + val selfDestructDisplayName = remember { mutableStateOf(chatModel.controller.appPrefs.selfDestructDisplayName.get() ?: "") } + val selfDestructDisplayNamePref = remember { chatModel.controller.appPrefs.selfDestructDisplayName } + + fun resetLAEnabled(onOff: Boolean) { + chatModel.controller.appPrefs.performLA.set(onOff) + chatModel.performLA.value = onOff + } + + fun disableUnavailableLA() { + resetLAEnabled(false) + currentLAMode.set(LAMode.SYSTEM) + laUnavailableInstructionAlert() + } + + fun resetSelfDestruct() { + selfDestructPref.set(false) + ksSelfDestructPassword.remove() + } + + fun toggleLAMode(toLAMode: LAMode) { + authenticate( + if (toLAMode == LAMode.SYSTEM) { + generalGetString(MR.strings.la_enter_app_passcode) + } else { + generalGetString(MR.strings.chat_lock) + }, + generalGetString(MR.strings.change_lock_mode) + ) { laResult -> + when (laResult) { + is LAResult.Error -> { + laFailedAlert() + } + is LAResult.Failed -> { /* Can be called multiple times on every failure */ } + LAResult.Success -> { + when (toLAMode) { + LAMode.SYSTEM -> { + authenticate(generalGetString(MR.strings.auth_enable_simplex_lock), promptSubtitle = "", usingLAMode = toLAMode) { laResult -> + when (laResult) { + LAResult.Success -> { + currentLAMode.set(toLAMode) + ksAppPassword.remove() + resetSelfDestruct() + laTurnedOnAlert() + } + is LAResult.Unavailable, is LAResult.Error -> laFailedAlert() + is LAResult.Failed -> { /* Can be called multiple times on every failure */ } + } + } + } + LAMode.PASSCODE -> { + ModalManager.shared.showCustomModal { close -> + Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + SetAppPasscodeView( + submit = { + laLockDelay.set(30) + currentLAMode.set(toLAMode) + passcodeAlert(generalGetString(MR.strings.passcode_set)) + }, + cancel = {}, + close = close + ) + } + } + } + } + } + is LAResult.Unavailable -> disableUnavailableLA() + } + } + } + + fun toggleSelfDestruct(selfDestruct: SharedPreference) { + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_mode)) { laResult -> + when (laResult) { + is LAResult.Error -> laFailedAlert() + is LAResult.Failed -> { /* Can be called multiple times on every failure */ } + LAResult.Success -> { + if (!selfDestruct.get()) { + ModalManager.shared.showCustomModal { close -> + EnableSelfDestruct(selfDestruct, close) + } + } else { + resetSelfDestruct() + } + } + is LAResult.Unavailable -> disableUnavailableLA() + } + } + } + + fun changeLAPassword() { + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.la_change_app_passcode)) { laResult -> + when (laResult) { + LAResult.Success -> { + ModalManager.shared.showCustomModal { close -> + Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + SetAppPasscodeView( + submit = { + passcodeAlert(generalGetString(MR.strings.passcode_changed)) + }, cancel = { + passcodeAlert(generalGetString(MR.strings.passcode_not_changed)) + }, close = close + ) + } + } + } + is LAResult.Error -> laFailedAlert() + is LAResult.Failed -> {} + is LAResult.Unavailable -> disableUnavailableLA() + } + } + } + + fun changeSelfDestructPassword() { + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_passcode)) { laResult -> + when (laResult) { + LAResult.Success -> { + ModalManager.shared.showCustomModal { close -> + Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + SetAppPasscodeView( + passcodeKeychain = ksSelfDestructPassword, + submit = { + selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_changed)) + }, cancel = { + passcodeAlert(generalGetString(MR.strings.passcode_not_changed)) + }, + close = close + ) + } + } + } + is LAResult.Error -> laFailedAlert() + is LAResult.Failed -> {} + is LAResult.Unavailable -> disableUnavailableLA() + } + } + } + + Column( + Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ) { + AppBarTitle(stringResource(MR.strings.chat_lock)) + SectionView { + EnableLock(performLA) { performLAToggle -> + performLA.value = performLAToggle + chatModel.controller.appPrefs.laNoticeShown.set(true) + if (performLAToggle) { + when (currentLAMode.state.value) { + LAMode.SYSTEM -> { + setPerformLA(true) + } + LAMode.PASSCODE -> { + ModalManager.shared.showCustomModal { close -> + Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + SetAppPasscodeView( + submit = { + laLockDelay.set(30) + chatModel.controller.appPrefs.performLA.set(true) + passcodeAlert(generalGetString(MR.strings.passcode_set)) + }, + cancel = { + resetLAEnabled(false) + }, + close = close + ) + } + } + } + } + } else { + setPerformLA(false) + } + } + LockModeSelector(laMode) { newLAMode -> + if (laMode.value == newLAMode) return@LockModeSelector + if (chatModel.controller.appPrefs.performLA.get()) { + toggleLAMode(newLAMode) + } else { + currentLAMode.set(newLAMode) + } + } + + if (performLA.value) { + LockDelaySelector(remember { laLockDelay.state }) { laLockDelay.set(it) } + if (showChangePasscode.value && laMode.value == LAMode.PASSCODE) { + SectionItemView({ changeLAPassword() }) { + Text( + generalGetString(MR.strings.la_change_app_passcode), + color = MaterialTheme.colors.primary + ) + } + } + } + if (performLA.value && laMode.value == LAMode.PASSCODE) { + SectionDividerSpaced() + SectionView(stringResource(MR.strings.self_destruct_passcode).uppercase()) { + val openInfo = { + ModalManager.shared.showModal { + SelfDestructInfoView() + } + } + SettingsActionItemWithContent(null, null, click = openInfo) { + SharedPreferenceToggleWithIcon( + stringResource(MR.strings.enable_self_destruct), + painterResource(MR.images.ic_info), + openInfo, + remember { selfDestructPref.state }.value + ) { + toggleSelfDestruct(selfDestructPref) + } + } + + if (remember { selfDestructPref.state }.value) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)) { + Text( + stringResource(MR.strings.self_destruct_new_display_name), + fontSize = 16.sp, + modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) + ) + ProfileNameField(selfDestructDisplayName, "", ::isValidDisplayName) + LaunchedEffect(selfDestructDisplayName.value) { + val new = selfDestructDisplayName.value + if (isValidDisplayName(new) && selfDestructDisplayNamePref.get() != new) { + selfDestructDisplayNamePref.set(new) + } + } + } + SectionItemView({ changeSelfDestructPassword() }) { + Text( + stringResource(MR.strings.change_self_destruct_passcode), + color = MaterialTheme.colors.primary + ) + } + } + } + } + } + SectionBottomSpacer() + } +} + +@Composable +private fun SelfDestructInfoView() { + Column( + Modifier.fillMaxWidth().verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING), + ) { + AppBarTitle(stringResource(MR.strings.self_destruct), withPadding = false) + ReadableText(stringResource(MR.strings.if_you_enter_self_destruct_code)) + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + TextListItem("1.", stringResource(MR.strings.all_app_data_will_be_cleared)) + TextListItem("2.", stringResource(MR.strings.app_passcode_replaced_with_self_destruct)) + TextListItem("3.", stringResource(MR.strings.empty_chat_profile_is_created)) + } + SectionBottomSpacer() + } +} + +@Composable +private fun EnableSelfDestruct( + selfDestruct: SharedPreference, + close: () -> Unit +) { + Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + SetAppPasscodeView( + passcodeKeychain = ksSelfDestructPassword, title = generalGetString(MR.strings.set_passcode), reason = generalGetString(MR.strings.enabled_self_destruct_passcode), + submit = { + selfDestruct.set(true) + selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_enabled)) + }, + cancel = {}, + close = close + ) + } +} + +@Composable +private fun EnableLock(performLA: MutableState, onCheckedChange: (Boolean) -> Unit) { + SectionItemView { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + stringResource(MR.strings.enable_lock), Modifier + .padding(end = 24.dp) + .fillMaxWidth() + .weight(1F) + ) + DefaultSwitch( + checked = performLA.value, + onCheckedChange = onCheckedChange, + ) + } + } +} + +@Composable +private fun LockModeSelector(state: State, onSelected: (LAMode) -> Unit) { + val values by remember { mutableStateOf(LAMode.values().map { it to it.text }) } + ExposedDropDownSettingRow( + generalGetString(MR.strings.lock_mode), + values, + state, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = onSelected + ) +} + +@Composable +private fun LockDelaySelector(state: State, onSelected: (Int) -> Unit) { + val delays = remember { if (laDelays.contains(state.value)) laDelays else listOf(state.value) + laDelays } + val values by remember { mutableStateOf(delays.map { it to laDelayText(it) }) } + ExposedDropDownSettingRow( + generalGetString(MR.strings.lock_after), + values, + state, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = onSelected + ) +} + +@Composable +private fun TextListItem(n: String, text: String) { + Box { + Text(n) + Text(text, Modifier.padding(start = 20.dp)) + } +} + +private fun laDelayText(t: Int): String { + val m = t / 60 + val s = t % 60 + return if (t == 0) { + generalGetString(MR.strings.la_immediately) + } else if (m == 0 || s != 0) { + // there are no options where both minutes and seconds are needed + generalGetString(MR.strings.la_seconds).format(s) + } else { + generalGetString(MR.strings.la_minutes).format(m) + } +} + +private fun passcodeAlert(title: String) { + AlertManager.shared.showAlertMsg( + title = title, + text = generalGetString(MR.strings.la_please_remember_to_store_password) + ) +} + +private fun selfDestructPasscodeAlert(title: String) { + AlertManager.shared.showAlertMsg(title, generalGetString(MR.strings.if_you_enter_passcode_data_removed)) +} + +fun laTurnedOnAlert() = AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.auth_simplex_lock_turned_on), + generalGetString(MR.strings.auth_you_will_be_required_to_authenticate_when_you_start_or_resume) +) + +fun laPasscodeNotSetAlert() = AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.lock_not_enabled), + generalGetString(MR.strings.you_can_turn_on_lock) +) + +fun laFailedAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.la_auth_failed), + text = generalGetString(MR.strings.la_could_not_be_verified) + ) +} + +fun laUnavailableInstructionAlert() = AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.auth_unavailable), + generalGetString(MR.strings.auth_device_authentication_is_not_enabled_you_can_turn_on_in_settings_once_enabled) +) + +fun laUnavailableTurningOffAlert() = AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.auth_unavailable), + generalGetString(MR.strings.auth_device_authentication_is_disabled_turning_off) +) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt index d4f8d1eccc..6b2cc49b0d 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt @@ -1,471 +1,33 @@ package chat.simplex.app.views.usersettings -import SectionBottomSpacer -import SectionDividerSpaced -import SectionItemView -import SectionTextFooter import SectionView import android.view.WindowManager -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier +import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext +import androidx.fragment.app.FragmentActivity +import chat.simplex.app.model.ChatController +import chat.simplex.app.model.ChatModel +import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.fragment.app.FragmentActivity -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.ProfileNameField -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword -import chat.simplex.app.views.helpers.DatabaseUtils.ksSelfDestructPassword -import chat.simplex.app.views.isValidDisplayName -import chat.simplex.app.views.localauth.SetAppPasscodeView -import chat.simplex.app.views.onboarding.ReadableText -import chat.simplex.res.MR - -enum class LAMode { - SYSTEM, - PASSCODE; - - val text: String - get() = when (this) { - SYSTEM -> generalGetString(MR.strings.la_mode_system) - PASSCODE -> generalGetString(MR.strings.la_mode_passcode) - } -} @Composable -fun PrivacySettingsView( - chatModel: ChatModel, +fun PrivacyDeviceSection( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - setPerformLA: (Boolean) -> Unit + setPerformLA: (Boolean) -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - ) { - val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode - AppBarTitle(stringResource(MR.strings.your_privacy)) - SectionView(stringResource(MR.strings.settings_section_title_device)) { - ChatLockItem(chatModel, showSettingsModal, setPerformLA) - val context = LocalContext.current - SettingsPreferenceItem(painterResource(MR.images.ic_visibility_off), stringResource(MR.strings.protect_app_screen), chatModel.controller.appPrefs.privacyProtectScreen) { on -> - if (on) { - (context as? FragmentActivity)?.window?.setFlags( - WindowManager.LayoutParams.FLAG_SECURE, - WindowManager.LayoutParams.FLAG_SECURE - ) - } else { - (context as? FragmentActivity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - } - } - SectionDividerSpaced() - - SectionView(stringResource(MR.strings.settings_section_title_chats)) { - SettingsPreferenceItem(painterResource(MR.images.ic_image), stringResource(MR.strings.auto_accept_images), chatModel.controller.appPrefs.privacyAcceptImages) - SettingsPreferenceItem(painterResource(MR.images.ic_travel_explore), stringResource(MR.strings.send_link_previews), chatModel.controller.appPrefs.privacyLinkPreviews) - SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = { - simplexLinkMode.set(it) - chatModel.simplexLinkMode.value = it - }) - } - if (chatModel.simplexLinkMode.value == SimplexLinkMode.BROWSER) { - SectionTextFooter(stringResource(MR.strings.simplex_link_mode_browser_warning)) - } - SectionBottomSpacer() - } -} - -@Composable -private fun SimpleXLinkOptions(simplexLinkModeState: State, onSelected: (SimplexLinkMode) -> Unit) { - val values = remember { - SimplexLinkMode.values().map { - when (it) { - SimplexLinkMode.DESCRIPTION -> it to generalGetString(MR.strings.simplex_link_mode_description) - SimplexLinkMode.FULL -> it to generalGetString(MR.strings.simplex_link_mode_full) - SimplexLinkMode.BROWSER -> it to generalGetString(MR.strings.simplex_link_mode_browser) - } - } - } - ExposedDropDownSettingRow( - generalGetString(MR.strings.simplex_link_mode), - values, - simplexLinkModeState, - icon = null, - enabled = remember { mutableStateOf(true) }, - onSelected = onSelected - ) -} - -private val laDelays = listOf(10, 30, 60, 180, 0) - -@Composable -fun SimplexLockView( - chatModel: ChatModel, - currentLAMode: SharedPreference, - setPerformLA: (Boolean) -> Unit -) { - val performLA = remember { chatModel.performLA } - val laMode = remember { chatModel.controller.appPrefs.laMode.state } - val laLockDelay = remember { chatModel.controller.appPrefs.laLockDelay } - val showChangePasscode = remember { derivedStateOf { performLA.value && currentLAMode.state.value == LAMode.PASSCODE } } - val activity = LocalContext.current as FragmentActivity - val selfDestructPref = remember { chatModel.controller.appPrefs.selfDestruct } - val selfDestructDisplayName = remember { mutableStateOf(chatModel.controller.appPrefs.selfDestructDisplayName.get() ?: "") } - val selfDestructDisplayNamePref = remember { chatModel.controller.appPrefs.selfDestructDisplayName } - - fun resetLAEnabled(onOff: Boolean) { - chatModel.controller.appPrefs.performLA.set(onOff) - chatModel.performLA.value = onOff - } - - fun disableUnavailableLA() { - resetLAEnabled(false) - currentLAMode.set(LAMode.SYSTEM) - laUnavailableInstructionAlert() - } - - fun resetSelfDestruct() { - selfDestructPref.set(false) - ksSelfDestructPassword.remove() - } - - fun toggleLAMode(toLAMode: LAMode) { - authenticate( - if (toLAMode == LAMode.SYSTEM) { - generalGetString(MR.strings.la_enter_app_passcode) + SectionView(stringResource(MR.strings.settings_section_title_device)) { + ChatLockItem(ChatModel, showSettingsModal, setPerformLA) + val context = LocalContext.current + SettingsPreferenceItem(painterResource(MR.images.ic_visibility_off), stringResource(MR.strings.protect_app_screen), ChatController.appPrefs.privacyProtectScreen) { on -> + if (on) { + (context as? FragmentActivity)?.window?.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) } else { - generalGetString(MR.strings.chat_lock) - }, - generalGetString(MR.strings.change_lock_mode) - ) { laResult -> - when (laResult) { - is LAResult.Error -> { - laFailedAlert() - } - is LAResult.Failed -> { /* Can be called multiple times on every failure */ } - LAResult.Success -> { - when (toLAMode) { - LAMode.SYSTEM -> { - authenticate(generalGetString(MR.strings.auth_enable_simplex_lock), promptSubtitle = "", usingLAMode = toLAMode) { laResult -> - when (laResult) { - LAResult.Success -> { - currentLAMode.set(toLAMode) - ksAppPassword.remove() - resetSelfDestruct() - laTurnedOnAlert() - } - is LAResult.Unavailable, is LAResult.Error -> laFailedAlert() - is LAResult.Failed -> { /* Can be called multiple times on every failure */ } - } - } - } - LAMode.PASSCODE -> { - ModalManager.shared.showCustomModal { close -> - Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - SetAppPasscodeView( - submit = { - laLockDelay.set(30) - currentLAMode.set(toLAMode) - passcodeAlert(generalGetString(MR.strings.passcode_set)) - }, - cancel = {}, - close = close - ) - } - } - } - } - } - is LAResult.Unavailable -> disableUnavailableLA() + (context as? FragmentActivity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) } } } - - fun toggleSelfDestruct(selfDestruct: SharedPreference) { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_mode)) { laResult -> - when (laResult) { - is LAResult.Error -> laFailedAlert() - is LAResult.Failed -> { /* Can be called multiple times on every failure */ } - LAResult.Success -> { - if (!selfDestruct.get()) { - ModalManager.shared.showCustomModal { close -> - EnableSelfDestruct(selfDestruct, close) - } - } else { - resetSelfDestruct() - } - } - is LAResult.Unavailable -> disableUnavailableLA() - } - } - } - - fun changeLAPassword() { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.la_change_app_passcode)) { laResult -> - when (laResult) { - LAResult.Success -> { - ModalManager.shared.showCustomModal { close -> - Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - SetAppPasscodeView( - submit = { - passcodeAlert(generalGetString(MR.strings.passcode_changed)) - }, cancel = { - passcodeAlert(generalGetString(MR.strings.passcode_not_changed)) - }, close = close - ) - } - } - } - is LAResult.Error -> laFailedAlert() - is LAResult.Failed -> {} - is LAResult.Unavailable -> disableUnavailableLA() - } - } - } - - fun changeSelfDestructPassword() { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_passcode)) { laResult -> - when (laResult) { - LAResult.Success -> { - ModalManager.shared.showCustomModal { close -> - Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - SetAppPasscodeView( - passcodeKeychain = ksSelfDestructPassword, - submit = { - selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_changed)) - }, cancel = { - passcodeAlert(generalGetString(MR.strings.passcode_not_changed)) - }, - close = close - ) - } - } - } - is LAResult.Error -> laFailedAlert() - is LAResult.Failed -> {} - is LAResult.Unavailable -> disableUnavailableLA() - } - } - } - - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - ) { - AppBarTitle(stringResource(MR.strings.chat_lock)) - SectionView { - EnableLock(performLA) { performLAToggle -> - performLA.value = performLAToggle - chatModel.controller.appPrefs.laNoticeShown.set(true) - if (performLAToggle) { - when (currentLAMode.state.value) { - LAMode.SYSTEM -> { - setPerformLA(true) - } - LAMode.PASSCODE -> { - ModalManager.shared.showCustomModal { close -> - Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - SetAppPasscodeView( - submit = { - laLockDelay.set(30) - chatModel.controller.appPrefs.performLA.set(true) - passcodeAlert(generalGetString(MR.strings.passcode_set)) - }, - cancel = { - resetLAEnabled(false) - }, - close = close - ) - } - } - } - } - } else { - setPerformLA(false) - } - } - LockModeSelector(laMode) { newLAMode -> - if (laMode.value == newLAMode) return@LockModeSelector - if (chatModel.controller.appPrefs.performLA.get()) { - toggleLAMode(newLAMode) - } else { - currentLAMode.set(newLAMode) - } - } - - if (performLA.value) { - LockDelaySelector(remember { laLockDelay.state }) { laLockDelay.set(it) } - if (showChangePasscode.value && laMode.value == LAMode.PASSCODE) { - SectionItemView({ changeLAPassword() }) { - Text( - generalGetString(MR.strings.la_change_app_passcode), - color = MaterialTheme.colors.primary - ) - } - } - } - if (performLA.value && laMode.value == LAMode.PASSCODE) { - SectionDividerSpaced() - SectionView(stringResource(MR.strings.self_destruct_passcode).uppercase()) { - val openInfo = { - ModalManager.shared.showModal { - SelfDestructInfoView() - } - } - SettingsActionItemWithContent(null, null, click = openInfo) { - SharedPreferenceToggleWithIcon( - stringResource(MR.strings.enable_self_destruct), - painterResource(MR.images.ic_info), - openInfo, - remember { selfDestructPref.state }.value - ) { - toggleSelfDestruct(selfDestructPref) - } - } - - if (remember { selfDestructPref.state }.value) { - Column(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)) { - Text( - stringResource(MR.strings.self_destruct_new_display_name), - fontSize = 16.sp, - modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) - ) - ProfileNameField(selfDestructDisplayName, "", ::isValidDisplayName) - LaunchedEffect(selfDestructDisplayName.value) { - val new = selfDestructDisplayName.value - if (isValidDisplayName(new) && selfDestructDisplayNamePref.get() != new) { - selfDestructDisplayNamePref.set(new) - } - } - } - SectionItemView({ changeSelfDestructPassword() }) { - Text( - stringResource(MR.strings.change_self_destruct_passcode), - color = MaterialTheme.colors.primary - ) - } - } - } - } - } - SectionBottomSpacer() - } -} - -@Composable -private fun SelfDestructInfoView() { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING), - ) { - AppBarTitle(stringResource(MR.strings.self_destruct), withPadding = false) - ReadableText(stringResource(MR.strings.if_you_enter_self_destruct_code)) - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - TextListItem("1.", stringResource(MR.strings.all_app_data_will_be_cleared)) - TextListItem("2.", stringResource(MR.strings.app_passcode_replaced_with_self_destruct)) - TextListItem("3.", stringResource(MR.strings.empty_chat_profile_is_created)) - } - SectionBottomSpacer() - } -} - -@Composable -private fun EnableSelfDestruct( - selfDestruct: SharedPreference, - close: () -> Unit -) { - Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - SetAppPasscodeView( - passcodeKeychain = ksSelfDestructPassword, title = generalGetString(MR.strings.set_passcode), reason = generalGetString(MR.strings.enabled_self_destruct_passcode), - submit = { - selfDestruct.set(true) - selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_enabled)) - }, - cancel = {}, - close = close - ) - } -} - -@Composable -private fun EnableLock(performLA: MutableState, onCheckedChange: (Boolean) -> Unit) { - SectionItemView { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - stringResource(MR.strings.enable_lock), Modifier - .padding(end = 24.dp) - .fillMaxWidth() - .weight(1F) - ) - DefaultSwitch( - checked = performLA.value, - onCheckedChange = onCheckedChange, - ) - } - } -} - -@Composable -private fun LockModeSelector(state: State, onSelected: (LAMode) -> Unit) { - val values by remember { mutableStateOf(LAMode.values().map { it to it.text }) } - ExposedDropDownSettingRow( - generalGetString(MR.strings.lock_mode), - values, - state, - icon = null, - enabled = remember { mutableStateOf(true) }, - onSelected = onSelected - ) -} - -@Composable -private fun LockDelaySelector(state: State, onSelected: (Int) -> Unit) { - val delays = remember { if (laDelays.contains(state.value)) laDelays else listOf(state.value) + laDelays } - val values by remember { mutableStateOf(delays.map { it to laDelayText(it) }) } - ExposedDropDownSettingRow( - generalGetString(MR.strings.lock_after), - values, - state, - icon = null, - enabled = remember { mutableStateOf(true) }, - onSelected = onSelected - ) -} - -@Composable -private fun TextListItem(n: String, text: String) { - Box { - Text(n) - Text(text, Modifier.padding(start = 20.dp)) - } -} - -private fun laDelayText(t: Int): String { - val m = t / 60 - val s = t % 60 - return if (t == 0) { - generalGetString(MR.strings.la_immediately) - } else if (m == 0 || s != 0) { - // there are no options where both minutes and seconds are needed - generalGetString(MR.strings.la_seconds).format(s) - } else { - generalGetString(MR.strings.la_minutes).format(m) - } -} - -private fun passcodeAlert(title: String) { - AlertManager.shared.showAlertMsg( - title = title, - text = generalGetString(MR.strings.la_please_remember_to_store_password) - ) -} - -private fun selfDestructPasscodeAlert(title: String) { - AlertManager.shared.showAlertMsg(title, generalGetString(MR.strings.if_you_enter_passcode_data_removed)) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt new file mode 100644 index 0000000000..c80c69ab92 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt @@ -0,0 +1,42 @@ +package chat.simplex.app.views.usersettings + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.dp +import chat.simplex.app.model.ServerAddress.Companion.parseServerAddress +import chat.simplex.app.model.ServerCfg +import chat.simplex.app.ui.theme.DEFAULT_PADDING +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.newchat.QRCodeScanner +import chat.simplex.res.MR + +@Composable +fun ScanProtocolServerLayout(onNext: (ServerCfg) -> Unit) { + Column( + Modifier + .fillMaxSize() + .padding(horizontal = DEFAULT_PADDING) + ) { + AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr), false) + Box( + Modifier + .fillMaxWidth() + .aspectRatio(ratio = 1F) + .padding(bottom = 12.dp) + ) { + QRCodeScanner { text -> + val res = parseServerAddress(text) + if (res != null) { + onNext(ServerCfg(text, false, null, true)) + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.smp_servers_invalid_address), + text = generalGetString(MR.strings.smp_servers_check_address) + ) + } + } + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt index 69f1e31435..443bce6dca 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt @@ -1,20 +1,10 @@ package chat.simplex.app.views.usersettings import android.Manifest -import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.ServerAddress.Companion.parseServerAddress import chat.simplex.app.model.ServerCfg -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCodeScanner import com.google.accompanist.permissions.rememberPermissionState -import chat.simplex.res.MR @Composable fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) { @@ -24,32 +14,3 @@ fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) { } ScanProtocolServerLayout(onNext) } - -@Composable -private fun ScanProtocolServerLayout(onNext: (ServerCfg) -> Unit) { - Column( - Modifier - .fillMaxSize() - .padding(horizontal = DEFAULT_PADDING) - ) { - AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr), false) - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - .padding(bottom = 12.dp) - ) { - QRCodeScanner { text -> - val res = parseServerAddress(text) - if (res != null) { - onNext(ServerCfg(text, false, null, true)) - } else { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.smp_servers_invalid_address), - text = generalGetString(MR.strings.smp_servers_check_address) - ) - } - } - } - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt new file mode 100644 index 0000000000..7c689c71a9 --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt @@ -0,0 +1,511 @@ +package chat.simplex.app.views.usersettings + +import SectionBottomSpacer +import SectionDividerSpaced +import SectionItemView +import SectionItemViewWithIcon +import SectionView +import TextIconSpaced +import android.content.res.Configuration +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.* +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.* +import androidx.work.WorkManager +import chat.simplex.app.* +import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.database.DatabaseView +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.onboarding.SimpleXInfo +import chat.simplex.app.views.onboarding.WhatsNewView +import chat.simplex.res.MR +import com.jakewharton.processphoenix.ProcessPhoenix + +@Composable +fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { + val user = chatModel.currentUser.value + val stopped = chatModel.chatRunning.value == false + + MaintainIncognitoState(chatModel) + + if (user != null) { + val requireAuth = remember { chatModel.controller.appPrefs.performLA.state } + SettingsLayout( + profile = user.profile, + stopped, + chatModel.chatDbEncrypted.value == true, + chatModel.incognito, + chatModel.controller.appPrefs.incognito, + user.displayName, + setPerformLA = setPerformLA, + showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } }, + showSettingsModal = { modalView -> { ModalManager.shared.showModal(true) { modalView(chatModel) } } }, + showSettingsModalWithSearch = { modalView -> + ModalManager.shared.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = true) { search.value = it } + }, + content = { modalView(chatModel, search) }) + } + }, + showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } }, + showVersion = { + withApi { + val info = chatModel.controller.apiGetVersion() + if (info != null) { + ModalManager.shared.showModal { VersionInfoView(info) } + } + } + }, + withAuth = { title, desc, block -> + if (!requireAuth.value) { + block() + } else { + var autoShow = true + ModalManager.shared.showModalCloseable { close -> + val onFinishAuth = { success: Boolean -> + if (success) { + close() + block() + } + } + + LaunchedEffect(Unit) { + if (autoShow) { + autoShow = false + runAuth(title, desc, onFinishAuth) + } + } + Box( + Modifier.fillMaxSize().background(MaterialTheme.colors.background), + contentAlignment = Alignment.Center + ) { + SimpleButton( + stringResource(MR.strings.auth_unlock), + icon = painterResource(MR.images.ic_lock), + click = { + runAuth(title, desc, onFinishAuth) + } + ) + } + } + } + }, + ) + } +} + +val simplexTeamUri = + "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D" + +@Composable +fun SettingsLayout( + profile: LocalProfile, + stopped: Boolean, + encrypted: Boolean, + incognito: MutableState, + incognitoPref: SharedPreference, + userDisplayName: String, + setPerformLA: (Boolean) -> Unit, + showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + showSettingsModalWithSearch: (@Composable (ChatModel, MutableState) -> Unit) -> Unit, + showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showVersion: () -> Unit, + withAuth: (title: String, desc: String, block: () -> Unit) -> Unit +) { + val theme = CurrentColors.collectAsState() + val uriHandler = LocalUriHandler.current + Box(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).themedBackground(theme.value.base)) { + Column( + Modifier + .fillMaxSize() + .padding(top = DEFAULT_PADDING) + ) { + AppBarTitle(stringResource(MR.strings.your_settings)) + + SectionView(stringResource(MR.strings.settings_section_title_you)) { + SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { + ProfilePreview(profile, stopped = stopped) + } + val profileHidden = rememberSaveable { mutableStateOf(false) } + SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden) } } }, disabled = stopped, extraPadding = true) + SettingsIncognitoActionItem(incognitoPref, incognito, stopped) { showModal { IncognitoView() }() } + SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) + ChatPreferencesItem(showCustomModal, stopped = stopped) + } + 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(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) + SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true) + DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) + } + SectionDividerSpaced() + + SectionView(stringResource(MR.strings.settings_section_title_help)) { + SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openUriCatching(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true) + } + SectionDividerSpaced() + + SectionView(stringResource(MR.strings.settings_section_title_support)) { + ContributeItem(uriHandler) + RateAppItem(uriHandler) + StarOnGithubItem(uriHandler) + } + SectionDividerSpaced() + + SettingsSectionApp(showSettingsModal, showCustomModal, showVersion, withAuth) + SectionBottomSpacer() + } + } +} + +@Composable +fun SettingsIncognitoActionItem( + incognitoPref: SharedPreference, + incognito: MutableState, + stopped: Boolean, + onClickInfo: () -> Unit, +) { + SettingsPreferenceItemWithInfo( + if (incognito.value) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy), + if (incognito.value) Indigo else MaterialTheme.colors.secondary, + stringResource(MR.strings.incognito), + stopped, + onClickInfo, + incognitoPref, + incognito + ) +} + +@Composable +fun MaintainIncognitoState(chatModel: ChatModel) { + // Cache previous value and once it changes in background, update it via API + var cachedIncognito by remember { mutableStateOf(chatModel.incognito.value) } + LaunchedEffect(chatModel.incognito.value) { + // Don't do anything if nothing changed + if (cachedIncognito == chatModel.incognito.value) return@LaunchedEffect + try { + chatModel.controller.apiSetIncognito(chatModel.incognito.value) + } catch (e: Exception) { + // Rollback the state + chatModel.controller.appPrefs.incognito.set(cachedIncognito) + // Crash the app + throw e + } + cachedIncognito = chatModel.incognito.value + } +} + +@Composable private fun DatabaseItem(encrypted: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) { + SectionItemViewWithIcon(openDatabaseView) { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(Modifier.weight(1f)) { + Icon( + painterResource(MR.images.ic_database), + contentDescription = stringResource(MR.strings.database_passphrase_and_export), + tint = if (encrypted) MaterialTheme.colors.secondary else WarningOrange, + ) + TextIconSpaced(true) + Text(stringResource(MR.strings.database_passphrase_and_export)) + } + if (stopped) { + Icon( + painterResource(MR.images.ic_report_filled), + contentDescription = stringResource(MR.strings.chat_is_stopped), + tint = Color.Red, + modifier = Modifier.padding(end = 6.dp) + ) + } + } + } +} + +@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit)), stopped: Boolean) { + SettingsActionItem( + painterResource(MR.images.ic_toggle_on), + stringResource(MR.strings.chat_preferences), + click = if (stopped) null else ({ + withApi { + showCustomModal { m, close -> + PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) + }() + } + }), + disabled = stopped, + extraPadding = true + ) +} + +@Composable +fun ChatLockItem( + chatModel: ChatModel, + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + setPerformLA: (Boolean) -> Unit +) { + val performLA = remember { chatModel.performLA } + val currentLAMode = remember { chatModel.controller.appPrefs.laMode } + SettingsActionItemWithContent( + click = showSettingsModal { SimplexLockView(chatModel, currentLAMode, setPerformLA) }, + icon = if (performLA.value) painterResource(MR.images.ic_lock_filled) else painterResource(MR.images.ic_lock), + text = stringResource(MR.strings.chat_lock), + iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary, + extraPadding = false, + ) { + Text(if (performLA.value) remember { currentLAMode.state }.value.text else generalGetString(MR.strings.la_mode_off), color = MaterialTheme.colors.secondary) + } +} + +@Composable private fun ContributeItem(uriHandler: UriHandler) { + SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) { + Icon( + painterResource(MR.images.ic_keyboard), + contentDescription = "GitHub", + tint = MaterialTheme.colors.secondary, + ) + TextIconSpaced(extraPadding = true) + Text(generalGetString(MR.strings.contribute), color = MaterialTheme.colors.primary) + } +} + +@Composable private fun RateAppItem(uriHandler: UriHandler) { + SectionItemViewWithIcon({ + runCatching { uriHandler.openUriCatching("market://details?id=chat.simplex.app") } + .onFailure { uriHandler.openUriCatching("https://play.google.com/store/apps/details?id=chat.simplex.app") } + } + ) { + Icon( + painterResource(MR.images.ic_star), + contentDescription = "Google Play", + tint = MaterialTheme.colors.secondary, + ) + TextIconSpaced(extraPadding = true) + Text(generalGetString(MR.strings.rate_the_app), color = MaterialTheme.colors.primary) + } +} + +@Composable private fun StarOnGithubItem(uriHandler: UriHandler) { + SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { + Icon( + painter = painterResource(MR.images.ic_github), + contentDescription = "GitHub", + tint = MaterialTheme.colors.secondary, + ) + TextIconSpaced(extraPadding = true) + Text(generalGetString(MR.strings.star_on_github), color = MaterialTheme.colors.primary) + } +} + +@Composable fun ChatConsoleItem(showTerminal: () -> Unit) { + SectionItemView(showTerminal) { + Icon( + painter = painterResource(MR.images.ic_outline_terminal), + contentDescription = stringResource(MR.strings.chat_console), + tint = MaterialTheme.colors.secondary, + ) + TextIconSpaced() + Text(stringResource(MR.strings.chat_console)) + } +} + +@Composable fun InstallTerminalAppItem(uriHandler: UriHandler) { + SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { + Icon( + painter = painterResource(MR.images.ic_github), + contentDescription = "GitHub", + tint = MaterialTheme.colors.secondary, + ) + TextIconSpaced() + Text(generalGetString(MR.strings.install_simplex_chat_for_terminal), color = MaterialTheme.colors.primary) + } +} + +@Composable fun AppVersionItem(showVersion: () -> Unit) { + SectionItemViewWithIcon(showVersion) { AppVersionText() } +} + +@Composable fun AppVersionText() { + Text("v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") +} + +@Composable fun ProfilePreview(profileOf: NamedChat, size: Dp = 60.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, textColor: Color = MaterialTheme.colors.onBackground, stopped: Boolean = false) { + ProfileImage(size = size, image = profileOf.image, color = iconColor) + Spacer(Modifier.padding(horizontal = 8.dp)) + Column(Modifier.height(size), verticalArrangement = Arrangement.Center) { + Text( + profileOf.displayName, + style = MaterialTheme.typography.caption, + fontWeight = FontWeight.Bold, + color = if (stopped) MaterialTheme.colors.secondary else textColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + if (profileOf.fullName.isNotEmpty()) { + Text( + profileOf.fullName, + Modifier.padding(vertical = 5.dp), + color = if (stopped) MaterialTheme.colors.secondary else textColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Composable +fun SettingsActionItem(icon: Painter, text: String, click: (() -> Unit)? = null, textColor: Color = Color.Unspecified, iconColor: Color = MaterialTheme.colors.secondary, disabled: Boolean = false, extraPadding: Boolean = false) { + SectionItemView(click, disabled = disabled, extraPadding = extraPadding) { + Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else iconColor) + TextIconSpaced(extraPadding) + Text(text, color = if (disabled) MaterialTheme.colors.secondary else textColor) + } +} + +@Composable +fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: (() -> Unit)? = null, iconColor: Color = MaterialTheme.colors.secondary, disabled: Boolean = false, extraPadding: Boolean = false, content: @Composable RowScope.() -> Unit) { + SectionItemView( + click, + extraPadding = extraPadding, + padding = if (extraPadding && icon != null) + PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING) + else + PaddingValues(horizontal = DEFAULT_PADDING), + disabled = disabled + ) { + if (icon != null) { + Icon(icon, text, Modifier, tint = if (disabled) MaterialTheme.colors.secondary else iconColor) + TextIconSpaced(extraPadding) + } + if (text != null) { + Text(text, Modifier.weight(1f), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground) + Spacer(Modifier.width(DEFAULT_PADDING)) + } + content() + } +} + +@Composable +fun SettingsPreferenceItem( + icon: Painter?, + text: String, + pref: SharedPreference, + iconColor: Color = MaterialTheme.colors.secondary, + enabled: Boolean = true, + onChange: ((Boolean) -> Unit)? = null, +) { + SettingsActionItemWithContent(icon, text, iconColor = iconColor,) { + SharedPreferenceToggle(pref, enabled, onChange) + } +} + +@Composable +fun SettingsPreferenceItemWithInfo( + icon: Painter, + iconTint: Color, + text: String, + stopped: Boolean, + onClickInfo: () -> Unit, + pref: SharedPreference, + prefState: MutableState? = null +) { + SettingsActionItemWithContent(icon, null, click = if (stopped) null else onClickInfo, iconColor = iconTint, extraPadding = true,) { + SharedPreferenceToggleWithIcon(text, painterResource(MR.images.ic_info), stopped, onClickInfo, pref, prefState) + } +} + +@Composable +fun PreferenceToggle( + text: String, + checked: Boolean, + onChange: (Boolean) -> Unit = {}, +) { + SettingsActionItemWithContent(null, text, extraPadding = true,) { + DefaultSwitch( + checked = checked, + onCheckedChange = onChange, + ) + } +} + +@Composable +fun PreferenceToggleWithIcon( + text: String, + icon: Painter? = null, + iconColor: Color? = MaterialTheme.colors.secondary, + checked: Boolean, + extraPadding: Boolean = false, + onChange: (Boolean) -> Unit = {}, +) { + SettingsActionItemWithContent(icon, text, iconColor = iconColor ?: MaterialTheme.colors.secondary, extraPadding = extraPadding) { + DefaultSwitch( + checked = checked, + onCheckedChange = { + onChange(it) + }, + ) + } +} + +private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> Unit) { + authenticate( + title, + desc, + completed = { laResult -> + onFinish(laResult == LAResult.Success || laResult is LAResult.Unavailable) + } + ) +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewSettingsLayout() { + SimpleXTheme { + SettingsLayout( + profile = LocalProfile.sampleData, + stopped = false, + encrypted = false, + incognito = remember { mutableStateOf(false) }, + incognitoPref = SharedPreference({ false }, {}), + userDisplayName = "Alice", + setPerformLA = { _ -> }, + showModal = { {} }, + showSettingsModal = { {} }, + showSettingsModalWithSearch = { }, + showCustomModal = { {} }, + showVersion = {}, + withAuth = { _, _, _ -> }, + ) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index b033f69f38..4b39102f97 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -1,495 +1,33 @@ package chat.simplex.app.views.usersettings -import SectionBottomSpacer -import SectionDividerSpaced -import SectionItemView -import SectionItemViewWithIcon import SectionView -import TextIconSpaced -import android.content.res.Configuration -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.* -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.* -import androidx.fragment.app.FragmentActivity +import androidx.compose.runtime.Composable import androidx.work.WorkManager -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.database.DatabaseView -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.SimpleXInfo -import chat.simplex.app.views.onboarding.WhatsNewView +import chat.simplex.app.SimplexApp +import chat.simplex.app.SimplexService +import chat.simplex.app.model.ChatModel +import chat.simplex.app.views.helpers.AlertManager +import chat.simplex.app.views.helpers.generalGetString import chat.simplex.res.MR import com.jakewharton.processphoenix.ProcessPhoenix +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource @Composable -fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { - val user = chatModel.currentUser.value - val stopped = chatModel.chatRunning.value == false - - MaintainIncognitoState(chatModel) - - if (user != null) { - val requireAuth = remember { chatModel.controller.appPrefs.performLA.state } - SettingsLayout( - profile = user.profile, - stopped, - chatModel.chatDbEncrypted.value == true, - chatModel.incognito, - chatModel.controller.appPrefs.incognito, - user.displayName, - setPerformLA = setPerformLA, - showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } }, - showSettingsModal = { modalView -> { ModalManager.shared.showModal(true) { modalView(chatModel) } } }, - showSettingsModalWithSearch = { modalView -> - ModalManager.shared.showCustomModal { close -> - val search = rememberSaveable { mutableStateOf("") } - ModalView( - { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = true) { search.value = it } - }, - content = { modalView(chatModel, search) }) - } - }, - showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } }, - showVersion = { - withApi { - val info = chatModel.controller.apiGetVersion() - if (info != null) { - ModalManager.shared.showModal { VersionInfoView(info) } - } - } - }, - withAuth = { title, desc, block -> - if (!requireAuth.value) { - block() - } else { - var autoShow = true - ModalManager.shared.showModalCloseable { close -> - val onFinishAuth = { success: Boolean -> - if (success) { - close() - block() - } - } - - LaunchedEffect(Unit) { - if (autoShow) { - autoShow = false - runAuth(title, desc, onFinishAuth) - } - } - Box( - Modifier.fillMaxSize().background(MaterialTheme.colors.background), - contentAlignment = Alignment.Center - ) { - SimpleButton( - stringResource(MR.strings.auth_unlock), - icon = painterResource(MR.images.ic_lock), - click = { - runAuth(title, desc, onFinishAuth) - } - ) - } - } - } - }, - ) - } -} - -val simplexTeamUri = - "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D" - -@Composable -fun SettingsLayout( - profile: LocalProfile, - stopped: Boolean, - encrypted: Boolean, - incognito: MutableState, - incognitoPref: SharedPreference, - userDisplayName: String, - setPerformLA: (Boolean) -> Unit, - showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), +fun SettingsSectionApp( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showSettingsModalWithSearch: (@Composable (ChatModel, MutableState) -> Unit) -> Unit, showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { - val theme = CurrentColors.collectAsState() - val uriHandler = LocalUriHandler.current - Box(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).themedBackground(theme.value.base)) { - Column( - Modifier - .fillMaxSize() - .padding(top = DEFAULT_PADDING) - ) { - AppBarTitle(stringResource(MR.strings.your_settings)) - - SectionView(stringResource(MR.strings.settings_section_title_you)) { - SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(profile, stopped = stopped) - } - val profileHidden = rememberSaveable { mutableStateOf(false) } - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden) } } }, disabled = stopped, extraPadding = true) - SettingsIncognitoActionItem(incognitoPref, incognito, stopped) { showModal { IncognitoView() }() } - SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) - ChatPreferencesItem(showCustomModal, stopped = stopped) - } - 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(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) - SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true) - DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) - } - SectionDividerSpaced() - - SectionView(stringResource(MR.strings.settings_section_title_help)) { - SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openUriCatching(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true) - } - SectionDividerSpaced() - - SectionView(stringResource(MR.strings.settings_section_title_support)) { - ContributeItem(uriHandler) - RateAppItem(uriHandler) - StarOnGithubItem(uriHandler) - } - SectionDividerSpaced() - - SectionView(stringResource(MR.strings.settings_section_title_app)) { - SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) }, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true) - AppVersionItem(showVersion) - } - SectionBottomSpacer() - } + SectionView(stringResource(MR.strings.settings_section_title_app)) { + SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) }, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true) + AppVersionItem(showVersion) } } -@Composable -fun SettingsIncognitoActionItem( - incognitoPref: SharedPreference, - incognito: MutableState, - stopped: Boolean, - onClickInfo: () -> Unit, -) { - SettingsPreferenceItemWithInfo( - if (incognito.value) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy), - if (incognito.value) Indigo else MaterialTheme.colors.secondary, - stringResource(MR.strings.incognito), - stopped, - onClickInfo, - incognitoPref, - incognito - ) -} - -@Composable -fun MaintainIncognitoState(chatModel: ChatModel) { - // Cache previous value and once it changes in background, update it via API - var cachedIncognito by remember { mutableStateOf(chatModel.incognito.value) } - LaunchedEffect(chatModel.incognito.value) { - // Don't do anything if nothing changed - if (cachedIncognito == chatModel.incognito.value) return@LaunchedEffect - try { - chatModel.controller.apiSetIncognito(chatModel.incognito.value) - } catch (e: Exception) { - // Rollback the state - chatModel.controller.appPrefs.incognito.set(cachedIncognito) - // Crash the app - throw e - } - cachedIncognito = chatModel.incognito.value - } -} - -@Composable private fun DatabaseItem(encrypted: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) { - SectionItemViewWithIcon(openDatabaseView) { - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row(Modifier.weight(1f)) { - Icon( - painterResource(MR.images.ic_database), - contentDescription = stringResource(MR.strings.database_passphrase_and_export), - tint = if (encrypted) MaterialTheme.colors.secondary else WarningOrange, - ) - TextIconSpaced(true) - Text(stringResource(MR.strings.database_passphrase_and_export)) - } - if (stopped) { - Icon( - painterResource(MR.images.ic_report_filled), - contentDescription = stringResource(MR.strings.chat_is_stopped), - tint = Color.Red, - modifier = Modifier.padding(end = 6.dp) - ) - } - } - } -} - -@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit)), stopped: Boolean) { - SettingsActionItem( - painterResource(MR.images.ic_toggle_on), - stringResource(MR.strings.chat_preferences), - click = if (stopped) null else ({ - withApi { - showCustomModal { m, close -> - PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) - }() - } - }), - disabled = stopped, - extraPadding = true - ) -} - -@Composable -fun ChatLockItem( - chatModel: ChatModel, - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - setPerformLA: (Boolean) -> Unit -) { - val performLA = remember { chatModel.performLA } - val currentLAMode = remember { chatModel.controller.appPrefs.laMode } - SettingsActionItemWithContent( - click = showSettingsModal { SimplexLockView(chatModel, currentLAMode, setPerformLA) }, - icon = if (performLA.value) painterResource(MR.images.ic_lock_filled) else painterResource(MR.images.ic_lock), - text = stringResource(MR.strings.chat_lock), - iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary, - extraPadding = false, - ) { - Text(if (performLA.value) remember { currentLAMode.state }.value.text else generalGetString(MR.strings.la_mode_off), color = MaterialTheme.colors.secondary) - } -} - -@Composable private fun ContributeItem(uriHandler: UriHandler) { - SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) { - Icon( - painterResource(MR.images.ic_keyboard), - contentDescription = "GitHub", - tint = MaterialTheme.colors.secondary, - ) - TextIconSpaced(extraPadding = true) - Text(generalGetString(MR.strings.contribute), color = MaterialTheme.colors.primary) - } -} - -@Composable private fun RateAppItem(uriHandler: UriHandler) { - SectionItemViewWithIcon({ - runCatching { uriHandler.openUriCatching("market://details?id=chat.simplex.app") } - .onFailure { uriHandler.openUriCatching("https://play.google.com/store/apps/details?id=chat.simplex.app") } - } - ) { - Icon( - painterResource(MR.images.ic_star), - contentDescription = "Google Play", - tint = MaterialTheme.colors.secondary, - ) - TextIconSpaced(extraPadding = true) - Text(generalGetString(MR.strings.rate_the_app), color = MaterialTheme.colors.primary) - } -} - -@Composable private fun StarOnGithubItem(uriHandler: UriHandler) { - SectionItemViewWithIcon({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { - Icon( - painter = painterResource(MR.images.ic_github), - contentDescription = "GitHub", - tint = MaterialTheme.colors.secondary, - ) - TextIconSpaced(extraPadding = true) - Text(generalGetString(MR.strings.star_on_github), color = MaterialTheme.colors.primary) - } -} - -@Composable fun ChatConsoleItem(showTerminal: () -> Unit) { - SectionItemView(showTerminal) { - Icon( - painter = painterResource(MR.images.ic_outline_terminal), - contentDescription = stringResource(MR.strings.chat_console), - tint = MaterialTheme.colors.secondary, - ) - TextIconSpaced() - Text(stringResource(MR.strings.chat_console)) - } -} - -@Composable fun InstallTerminalAppItem(uriHandler: UriHandler) { - SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { - Icon( - painter = painterResource(MR.images.ic_github), - contentDescription = "GitHub", - tint = MaterialTheme.colors.secondary, - ) - TextIconSpaced() - Text(generalGetString(MR.strings.install_simplex_chat_for_terminal), color = MaterialTheme.colors.primary) - } -} - -@Composable private fun AppVersionItem(showVersion: () -> Unit) { - SectionItemViewWithIcon(showVersion) { AppVersionText() } -} - -@Composable fun AppVersionText() { - Text("v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") -} - -@Composable fun ProfilePreview(profileOf: NamedChat, size: Dp = 60.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, textColor: Color = MaterialTheme.colors.onBackground, stopped: Boolean = false) { - ProfileImage(size = size, image = profileOf.image, color = iconColor) - Spacer(Modifier.padding(horizontal = 8.dp)) - Column(Modifier.height(size), verticalArrangement = Arrangement.Center) { - Text( - profileOf.displayName, - style = MaterialTheme.typography.caption, - fontWeight = FontWeight.Bold, - color = if (stopped) MaterialTheme.colors.secondary else textColor, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - if (profileOf.fullName.isNotEmpty()) { - Text( - profileOf.fullName, - Modifier.padding(vertical = 5.dp), - color = if (stopped) MaterialTheme.colors.secondary else textColor, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } -} - -@Composable -fun SettingsActionItem(icon: Painter, text: String, click: (() -> Unit)? = null, textColor: Color = Color.Unspecified, iconColor: Color = MaterialTheme.colors.secondary, disabled: Boolean = false, extraPadding: Boolean = false) { - SectionItemView(click, disabled = disabled, extraPadding = extraPadding) { - Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else iconColor) - TextIconSpaced(extraPadding) - Text(text, color = if (disabled) MaterialTheme.colors.secondary else textColor) - } -} - -@Composable -fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: (() -> Unit)? = null, iconColor: Color = MaterialTheme.colors.secondary, disabled: Boolean = false, extraPadding: Boolean = false, content: @Composable RowScope.() -> Unit) { - SectionItemView( - click, - extraPadding = extraPadding, - padding = if (extraPadding && icon != null) - PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING) - else - PaddingValues(horizontal = DEFAULT_PADDING), - disabled = disabled - ) { - if (icon != null) { - Icon(icon, text, Modifier, tint = if (disabled) MaterialTheme.colors.secondary else iconColor) - TextIconSpaced(extraPadding) - } - if (text != null) { - Text(text, Modifier.weight(1f), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING)) - } - content() - } -} - -@Composable -fun SettingsPreferenceItem( - icon: Painter?, - text: String, - pref: SharedPreference, - iconColor: Color = MaterialTheme.colors.secondary, - enabled: Boolean = true, - onChange: ((Boolean) -> Unit)? = null, -) { - SettingsActionItemWithContent(icon, text, iconColor = iconColor,) { - SharedPreferenceToggle(pref, enabled, onChange) - } -} - -@Composable -fun SettingsPreferenceItemWithInfo( - icon: Painter, - iconTint: Color, - text: String, - stopped: Boolean, - onClickInfo: () -> Unit, - pref: SharedPreference, - prefState: MutableState? = null -) { - SettingsActionItemWithContent(icon, null, click = if (stopped) null else onClickInfo, iconColor = iconTint, extraPadding = true,) { - SharedPreferenceToggleWithIcon(text, painterResource(MR.images.ic_info), stopped, onClickInfo, pref, prefState) - } -} - -@Composable -fun PreferenceToggle( - text: String, - checked: Boolean, - onChange: (Boolean) -> Unit = {}, -) { - SettingsActionItemWithContent(null, text, extraPadding = true,) { - DefaultSwitch( - checked = checked, - onCheckedChange = onChange, - ) - } -} - -@Composable -fun PreferenceToggleWithIcon( - text: String, - icon: Painter? = null, - iconColor: Color? = MaterialTheme.colors.secondary, - checked: Boolean, - extraPadding: Boolean = false, - onChange: (Boolean) -> Unit = {}, -) { - SettingsActionItemWithContent(icon, text, iconColor = iconColor ?: MaterialTheme.colors.secondary, extraPadding = extraPadding) { - DefaultSwitch( - checked = checked, - onCheckedChange = { - onChange(it) - }, - ) - } -} - -private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> Unit) { - authenticate( - title, - desc, - completed = { laResult -> - onFinish(laResult == LAResult.Success || laResult is LAResult.Unavailable) - } - ) -} - private fun restartApp() { ProcessPhoenix.triggerRebirth(SimplexApp.context) shutdownApp() @@ -509,30 +47,3 @@ private fun shutdownAppAlert(onConfirm: () -> Unit) { onConfirm = onConfirm ) } - -@Preview(showBackground = true) -@Preview( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -) -@Composable -fun PreviewSettingsLayout() { - SimpleXTheme { - SettingsLayout( - profile = LocalProfile.sampleData, - stopped = false, - encrypted = false, - incognito = remember { mutableStateOf(false) }, - incognitoPref = SharedPreference({ false }, {}), - userDisplayName = "Alice", - setPerformLA = { _ -> }, - showModal = { {} }, - showSettingsModal = { {} }, - showSettingsModalWithSearch = { }, - showCustomModal = { {} }, - showVersion = {}, - withAuth = { _, _, _ -> }, - ) - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt index 2cd8484296..9b60ff465c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt @@ -20,9 +20,10 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R import chat.simplex.app.TAG import chat.simplex.app.model.* +import chat.simplex.app.platform.sendEmail +import chat.simplex.app.platform.shareText import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.ShareAddressButton import chat.simplex.app.views.helpers.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt index ee03a689ec..9a4dca4125 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt @@ -19,8 +19,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.platform.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.ProfileNameField import chat.simplex.app.views.helpers.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt index 49024f5977..db3174ad9f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt @@ -21,8 +21,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import chat.simplex.app.R -import chat.simplex.app.chatPasswordHash import chat.simplex.app.model.* +import chat.simplex.app.platform.chatPasswordHash import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.item.ItemAction import chat.simplex.app.views.chatlist.UserProfilePickerItem diff --git a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c index 9a3c9c48ab..8b2977a3e8 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c @@ -19,7 +19,7 @@ extern void __rel_iplt_start(void){}; extern void reallocarray(void){}; JNIEXPORT jint JNICALL -Java_chat_simplex_app_SimplexAppKt_pipeStdOutToSocket(JNIEnv *env, __unused jclass clazz, jstring socket_name) { +Java_chat_simplex_app_platform_Backend_1commonKt_pipeStdOutToSocket(JNIEnv *env, __unused jclass clazz, jstring socket_name) { const char *name = (*env)->GetStringUTFChars(env, socket_name, JNI_FALSE); int ret = pipe_std_to_socket(name); (*env)->ReleaseStringUTFChars(env, socket_name, name); @@ -27,7 +27,7 @@ Java_chat_simplex_app_SimplexAppKt_pipeStdOutToSocket(JNIEnv *env, __unused jcla } JNIEXPORT void JNICALL -Java_chat_simplex_app_SimplexAppKt_initHS(__unused JNIEnv *env, __unused jclass clazz) { +Java_chat_simplex_app_platform_Backend_1commonKt_initHS(__unused JNIEnv *env, __unused jclass clazz) { hs_init(NULL, NULL); setLineBuffering(); } @@ -44,7 +44,7 @@ extern char *chat_parse_server(const char *str); extern char *chat_password_hash(const char *pwd, const char *salt); JNIEXPORT jobjectArray JNICALL -Java_chat_simplex_app_SimplexAppKt_chatMigrateInit(JNIEnv *env, __unused jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) { +Java_chat_simplex_app_platform_Backend_1commonKt_chatMigrateInit(JNIEnv *env, __unused jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) { const char *_dbPath = (*env)->GetStringUTFChars(env, dbPath, JNI_FALSE); const char *_dbKey = (*env)->GetStringUTFChars(env, dbKey, JNI_FALSE); const char *_confirm = (*env)->GetStringUTFChars(env, confirm, JNI_FALSE); @@ -67,7 +67,7 @@ Java_chat_simplex_app_SimplexAppKt_chatMigrateInit(JNIEnv *env, __unused jclass } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_SimplexAppKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { +Java_chat_simplex_app_platform_Backend_1commonKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg)); (*env)->ReleaseStringUTFChars(env, msg, _msg); @@ -75,17 +75,17 @@ Java_chat_simplex_app_SimplexAppKt_chatSendCmd(JNIEnv *env, __unused jclass claz } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_SimplexAppKt_chatRecvMsg(JNIEnv *env, __unused jclass clazz, jlong controller) { +Java_chat_simplex_app_platform_Backend_1commonKt_chatRecvMsg(JNIEnv *env, __unused jclass clazz, jlong controller) { return (*env)->NewStringUTF(env, chat_recv_msg((void*)controller)); } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_SimplexAppKt_chatRecvMsgWait(JNIEnv *env, __unused jclass clazz, jlong controller, jint wait) { +Java_chat_simplex_app_platform_Backend_1commonKt_chatRecvMsgWait(JNIEnv *env, __unused jclass clazz, jlong controller, jint wait) { return (*env)->NewStringUTF(env, chat_recv_msg_wait((void*)controller, wait)); } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_SimplexAppKt_chatParseMarkdown(JNIEnv *env, __unused jclass clazz, jstring str) { +Java_chat_simplex_app_platform_Backend_1commonKt_chatParseMarkdown(JNIEnv *env, __unused jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_parse_markdown(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); @@ -93,7 +93,7 @@ Java_chat_simplex_app_SimplexAppKt_chatParseMarkdown(JNIEnv *env, __unused jclas } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_SimplexAppKt_chatParseServer(JNIEnv *env, __unused jclass clazz, jstring str) { +Java_chat_simplex_app_platform_Backend_1commonKt_chatParseServer(JNIEnv *env, __unused jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_parse_server(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); @@ -101,7 +101,7 @@ Java_chat_simplex_app_SimplexAppKt_chatParseServer(JNIEnv *env, __unused jclass } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_SimplexAppKt_chatPasswordHash(JNIEnv *env, __unused jclass clazz, jstring pwd, jstring salt) { +Java_chat_simplex_app_platform_Backend_1commonKt_chatPasswordHash(JNIEnv *env, __unused jclass clazz, jstring pwd, jstring salt) { const char *_pwd = (*env)->GetStringUTFChars(env, pwd, JNI_FALSE); const char *_salt = (*env)->GetStringUTFChars(env, salt, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_password_hash(_pwd, _salt)); From 38f40fec3d764605c2eb92b698a45e5b79d20650 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:42:10 +0300 Subject: [PATCH 02/18] multiplatform: split common/android/desktop (#2672) * multiplatform: relocated code to its new place * code becomes better * renamed file * fixes for BASE64 and images, and changes for appFileUri * different Base64 for both platforms * fix file saving on long click * platformCallbacks refactoring * renamed callbacks to platform * eol --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/multiplatform/android/build.gradle.kts | 11 +- .../main/java/chat/simplex/app/BackupAgent.kt | 4 +- .../java/chat/simplex/app/MainActivity.kt | 94 ++++--------- .../helpers => }/MessagesFetcherWorker.kt | 6 +- .../main/java/chat/simplex/app/SimplexApp.kt | 101 +++++++++++-- .../java/chat/simplex/app/SimplexService.kt | 40 +++--- .../java/chat/simplex/app/model/NtfManager.kt | 36 +++-- .../simplex/app/platform/AppCommon.common.kt | 30 ---- .../chat/simplex/app/platform/Files.common.kt | 30 ---- .../chat/simplex/app/platform/Resources.kt | 9 -- .../app/views/call/IncomingCallActivity.kt | 42 +++--- .../chat/simplex/app/views/newchat/QRCode.kt | 17 --- apps/multiplatform/build.gradle.kts | 1 - apps/multiplatform/common/build.gradle.kts | 43 +++--- .../chat/simplex/common/helpers/Extensions.kt | 22 +++ .../chat/simplex/common/helpers/Locale.kt} | 18 +-- .../simplex/common/helpers}/SoundPlayer.kt | 24 ++-- .../common/platform/AppCommon.android.kt} | 19 ++- .../simplex/common/platform/Back.android.kt | 9 ++ .../common/platform/Cryptor.android.kt} | 19 ++- .../simplex/common/platform/Files.android.kt | 40 ++++++ .../common/platform/Images.android.kt} | 64 +++++---- .../simplex/common/platform/Log.android.kt | 10 ++ .../common/platform/Modifier.android.kt | 16 +++ .../common/platform/Notifications.android.kt | 3 + .../platform/PlatformTextField.android.kt} | 26 ++-- .../common/platform/RecAndPlay.android.kt} | 59 ++++---- .../common/platform/Resources.android.kt | 51 +++++++ .../simplex/common/platform/Share.android.kt} | 57 ++++---- .../simplex/common/platform/UI.android.kt} | 30 ++-- .../common/platform/VideoPlayer.android.kt} | 106 +++++++------- .../simplex/common/ui/theme/Type.android.kt} | 5 +- .../common/views/call/CallView.android.kt} | 41 +++--- .../common/views/chat/ComposeView.android.kt} | 37 +++-- .../views/chat/ScanCodeView.android.kt} | 5 +- .../common/views/chat/SendMsgView.android.kt} | 6 +- .../views/chat/item/CIImageView.android.kt} | 34 ++--- .../views/chat/item/CIVideoView.android.kt} | 25 ++-- .../views/chat/item/ChatItemView.android.kt} | 15 +- .../chat/item/ImageFullScreenView.android.kt} | 21 ++- .../views/helpers/DefaultDialog.android.kt | 16 +++ .../helpers/DefaultDropDownMenu.android.kt | 46 ++++++ .../views/helpers/GetImageView.android.kt} | 28 ++-- .../helpers/LocalAuthentication.android.kt} | 13 +- .../common/views/helpers/Utils.android.kt} | 82 ++++++----- .../common/views/newchat/QRCode.android.kt | 18 +++ .../views/newchat/QRCodeScanner.android.kt} | 11 +- .../newchat/ScanToConnectView.android.kt} | 6 +- .../SetNotificationsMode.android.kt} | 10 +- .../views/usersettings/Appearance.android.kt} | 89 +++++------- .../usersettings/PrivacySettings.android.kt} | 11 +- .../ScanProtocolServer.android.kt} | 6 +- .../usersettings/SettingsView.android.kt} | 20 +-- .../res/drawable/edit_text_cursor.xml | 0 .../src/commonMain/cpp/android/simplex-api.c | 18 +-- .../src/commonMain/cpp/desktop/simplex-api.c | 16 +-- .../kotlin/chat/simplex/common}/App.kt | 81 ++++++----- .../kotlin/chat/simplex/common}/AppLock.kt | 47 +++++-- .../chat/simplex/common}/model/ChatModel.kt | 44 ++++-- .../chat/simplex/common}/model/SimpleXAPI.kt | 119 ++++++++-------- .../chat/simplex/common/platform/AppCommon.kt | 47 +++++++ .../chat/simplex/common/platform/Back.kt | 7 + .../chat/simplex/common/platform/Core.kt} | 19 +-- .../chat/simplex/common/platform/Cryptor.kt | 9 ++ .../chat/simplex/common/platform/Files.kt | 80 +++++++++++ .../chat/simplex/common/platform/Images.kt | 24 ++++ .../chat/simplex/common/platform/Log.kt | 10 ++ .../chat/simplex/common/platform/Modifier.kt | 13 ++ .../simplex/common/platform/Notifications.kt | 3 + .../simplex/common/platform/NtfManager.kt | 23 +++ .../chat/simplex/common/platform/Platform.kt | 26 ++++ .../common/platform/PlatformTextField.kt | 15 ++ .../simplex/common/platform/RecAndPlay.kt | 42 ++++++ .../chat/simplex/common/platform/Resources.kt | 31 ++++ .../chat/simplex/common/platform/Share.kt | 9 ++ .../kotlin/chat/simplex/common/platform/UI.kt | 16 +++ .../simplex/common/platform/VideoPlayer.kt | 37 +++++ .../chat/simplex/common}/ui/theme/Color.kt | 2 +- .../chat/simplex/common}/ui/theme/Shape.kt | 2 +- .../chat/simplex/common}/ui/theme/Theme.kt | 12 +- .../simplex/common}/ui/theme/ThemeManager.kt | 18 +-- .../chat/simplex/common/ui/theme/Type.kt} | 4 +- .../chat/simplex/common/views/Preview.kt | 7 + .../chat/simplex/common}/views/SplashView.kt | 2 +- .../simplex/common}/views/TerminalView.kt | 42 +++--- .../chat/simplex/common}/views/WelcomeView.kt | 19 ++- .../simplex/common}/views/call/CallManager.kt | 19 ++- .../simplex/common/views/call/CallView.kt | 6 + .../views/call/IncomingCallAlertView.kt | 26 ++-- .../chat/simplex/common}/views/call/WebRTC.kt | 6 +- .../common}/views/chat/ChatInfoView.kt | 28 ++-- .../common}/views/chat/ChatItemInfoView.kt | 25 ++-- .../simplex/common}/views/chat/ChatView.kt | 65 ++++----- .../common}/views/chat/ComposeFileView.kt | 7 +- .../common}/views/chat/ComposeImageView.kt | 14 +- .../simplex/common/views/chat/ComposeView.kt} | 85 ++++++----- .../common}/views/chat/ComposeVoiceView.kt | 12 +- .../common}/views/chat/ContactPreferences.kt | 11 +- .../common}/views/chat/ContextItemView.kt | 11 +- .../common/views/chat/ScanCodeView.kt} | 11 +- .../simplex/common/views/chat/SendMsgView.kt} | 50 +++---- .../common}/views/chat/VerifyCodeView.kt | 14 +- .../views/chat/group/AddGroupMembersView.kt | 24 ++-- .../views/chat/group/GroupChatInfoView.kt | 27 ++-- .../common}/views/chat/group/GroupLinkView.kt | 16 ++- .../views/chat/group/GroupMemberInfoView.kt | 32 ++--- .../views/chat/group/GroupPreferences.kt | 11 +- .../views/chat/group/GroupProfileView.kt | 34 ++--- .../views/chat/group/WelcomeMessageView.kt | 21 +-- .../common}/views/chat/item/CICallItemView.kt | 8 +- .../views/chat/item/CIChatFeatureView.kt | 5 +- .../common}/views/chat/item/CIEventView.kt | 14 +- .../chat/item/CIFeaturePreferenceView.kt | 7 +- .../common}/views/chat/item/CIFileView.kt | 58 +++----- .../views/chat/item/CIGroupInvitationView.kt | 24 ++-- .../common/views/chat/item/CIImageView.kt} | 49 ++++--- .../views/chat/item/CIInvalidJSONView.kt | 14 +- .../common}/views/chat/item/CIMetaView.kt | 12 +- .../views/chat/item/CIRcvDecryptionError.kt | 10 +- .../common/views/chat/item/CIVIdeoView.kt} | 49 +++---- .../common}/views/chat/item/CIVoiceView.kt | 18 +-- .../common/views/chat/item/ChatItemView.kt} | 37 ++--- .../views/chat/item/DeletedItemView.kt | 14 +- .../common}/views/chat/item/EmojiItemView.kt | 4 +- .../common}/views/chat/item/FramedItemView.kt | 23 +-- .../views/chat/item/ImageFullScreenView.kt} | 50 +++---- .../views/chat/item/IntegrityErrorItemView.kt | 23 ++- .../views/chat/item/MarkedDeletedItemView.kt | 20 ++- .../common}/views/chat/item/TextItemView.kt | 55 ++++---- .../common}/views/chatlist/ChatHelpView.kt | 21 ++- .../views/chatlist/ChatListNavLinkView.kt | 46 +++--- .../common}/views/chatlist/ChatListView.kt | 35 +++-- .../common}/views/chatlist/ChatPreviewView.kt | 24 ++-- .../views/chatlist/ContactConnectionView.kt | 10 +- .../views/chatlist/ContactRequestView.kt | 10 +- .../views/chatlist/ShareListNavLinkView.kt | 8 +- .../common}/views/chatlist/ShareListView.kt | 17 +-- .../common}/views/chatlist/UserPicker.kt | 25 ++-- .../common}/views/database/ChatArchiveView.kt | 64 +++------ .../views/database/DatabaseEncryptionView.kt | 13 +- .../views/database/DatabaseErrorView.kt | 39 ++--- .../common}/views/database/DatabaseView.kt | 126 ++++++----------- .../common}/views/helpers/AlertManager.kt | 13 +- .../common}/views/helpers/AnimationUtils.kt | 2 +- .../common}/views/helpers/ChatInfoImage.kt | 15 +- .../views/helpers/ChooseAttachmentView.kt | 8 +- .../common}/views/helpers/CloseSheetBar.kt | 12 +- .../common}/views/helpers/CustomIcons.kt | 2 +- .../common}/views/helpers/CustomTimePicker.kt | 10 +- .../common}/views/helpers/DataClasses.kt | 2 +- .../common}/views/helpers/DatabaseUtils.kt | 27 ++-- .../views/helpers/DefaultBasicTextField.kt | 6 +- .../common/views/helpers/DefaultDialog.kt | 9 ++ .../views/helpers/DefaultDropdownMenu.kt} | 26 +++- .../common}/views/helpers/DefaultSwitch.kt | 2 +- .../common}/views/helpers/DefaultTopAppBar.kt | 5 +- .../simplex/common}/views/helpers/Enums.kt | 24 ++-- .../helpers/ExposedDropDownSettingRow.kt | 9 +- .../common}/views/helpers/GestureDetector.kt | 6 +- .../common/views/helpers/GetImageView.kt | 13 ++ .../common}/views/helpers/LinkPreviews.kt | 41 +++--- .../views/helpers/LocalAuthentication.kt} | 25 +++- .../common}/views/helpers/ModalView.kt | 15 +- .../common}/views/helpers/Modifiers.kt | 2 +- .../common}/views/helpers/SearchTextField.kt | 3 +- .../simplex/common}/views/helpers/Section.kt | 14 +- .../common}/views/helpers/SimpleButton.kt | 7 +- .../common}/views/helpers/TextEditor.kt | 17 +-- .../simplex/common/views/helpers/Utils.kt} | 133 +++++++++++------- .../common}/views/localauth/LocalAuthView.kt | 24 ++-- .../common}/views/localauth/PasscodeView.kt | 15 +- .../common}/views/localauth/PasswordEntry.kt | 2 +- .../views/localauth/SetAppPasscodeView.kt | 11 +- .../views/newchat/AddContactLearnMore.kt | 9 +- .../common}/views/newchat/AddContactView.kt | 22 +-- .../common}/views/newchat/AddGroupView.kt | 34 ++--- .../views/newchat/ConnectViaLinkView.kt | 6 +- .../newchat/ContactConnectionInfoView.kt | 30 ++-- .../common}/views/newchat/CreateLinkView.kt | 12 +- .../common}/views/newchat/NewChatSheet.kt | 21 ++- .../common}/views/newchat/PasteToConnect.kt | 34 ++--- .../simplex/common/views/newchat/QRCode.kt} | 21 ++- .../common/views/newchat/QRCodeScanner.kt | 6 + .../views/newchat/ScanToConnectView.kt} | 46 +++--- .../views/onboarding/CreateSimpleXAddress.kt | 25 ++-- .../common}/views/onboarding/HowItWorks.kt | 24 ++-- .../views/onboarding/OnboardingView.kt | 10 +- .../views/onboarding/SetNotificationsMode.kt} | 14 +- .../common}/views/onboarding/SimpleXInfo.kt | 21 ++- .../common}/views/onboarding/WhatsNewView.kt | 22 +-- .../usersettings/AdvancedNetworkSettings.kt | 19 ++- .../common/views/usersettings/Appearance.kt} | 112 ++++++--------- .../views/usersettings/CallSettings.kt | 7 +- .../views/usersettings/DeveloperView.kt | 9 +- .../common}/views/usersettings/HelpView.kt | 20 ++- .../views/usersettings/HiddenProfileView.kt | 15 +- .../views/usersettings/IncognitoView.kt | 9 +- .../views/usersettings/MarkdownHelpView.kt | 21 ++- .../views/usersettings/NetworkAndServers.kt | 17 +-- .../usersettings/NotificationsSettingsView.kt | 65 +++------ .../common}/views/usersettings/Preferences.kt | 7 +- .../views/usersettings/PrivacySettings.kt} | 54 ++++--- .../views/usersettings/ProtocolServerView.kt | 19 ++- .../views/usersettings/ProtocolServersView.kt | 11 +- .../common}/views/usersettings/RTCServers.kt | 11 +- .../views/usersettings/ScanProtocolServer.kt} | 15 +- .../views/usersettings/SettingsView.kt} | 48 ++++--- .../usersettings/UserAddressLearnMore.kt | 9 +- .../views/usersettings/UserAddressView.kt | 40 +++--- .../views/usersettings/UserProfileView.kt | 36 +++-- .../views/usersettings/UserProfilesView.kt | 22 +-- .../views/usersettings/VersionInfoView.kt | 21 +-- .../kotlin/chat/simplex/common/DesktopApp.kt | 127 +++++++++++++++++ .../common/platform/AppCommon.desktop.kt | 67 +++++++++ .../simplex/common/platform/Back.desktop.kt | 14 ++ .../common/platform/Cryptor.desktop.kt | 15 ++ .../simplex/common/platform/Files.desktop.kt | 42 ++++++ .../simplex/common/platform/Images.desktop.kt | 129 +++++++++++++++++ .../simplex/common/platform/Log.desktop.kt | 8 ++ .../common/platform/Modifier.desktop.kt | 15 ++ .../common/platform/Notifications.desktop.kt | 5 + .../common/platform/Platform.desktop.kt | 28 ++++ .../platform/PlatformTextField.desktop.kt | 107 ++++++++++++++ .../common/platform/RecAndPlay.desktop.kt | 53 +++++++ .../common/platform/Resources.desktop.kt | 58 ++++++++ .../simplex/common/platform/Share.desktop.kt | 31 ++++ .../simplex/common/platform/UI.desktop.kt | 19 +++ .../common/platform/VideoPlayer.desktop.kt | 50 +++++++ .../simplex/common/ui/theme/Type.desktop.kt | 14 ++ .../common/views/call/CallView.desktop.kt | 8 ++ .../common/views/chat/ComposeView.desktop.kt | 24 ++++ .../common/views/chat/ScanCodeView.desktop.kt | 8 ++ .../common/views/chat/SendMsgView.desktop.kt | 11 ++ .../views/chat/item/CIImageView.desktop.kt | 28 ++++ .../views/chat/item/CIVideoView.desktop.kt | 23 +++ .../views/chat/item/ChatItemView.desktop.kt | 23 +++ .../chat/item/ImageFullScreenView.desktop.kt | 28 ++++ .../views/helpers/DefaultDialog.desktop.kt | 119 ++++++++++++++++ .../helpers/DefaultDropDownMenu.desktop.kt | 34 +++++ .../views/helpers/GetImageView.desktop.kt | 57 ++++++++ .../helpers/LocalAuthentication.desktop.kt | 16 +++ .../common/views/helpers/Utils.desktop.kt | 72 ++++++++++ .../common/views/newchat/QRCode.desktop.kt | 18 +++ .../views/newchat/QRCodeScanner.desktop.kt | 8 ++ .../newchat/ScanToConnectView.desktop.kt | 12 ++ .../SetNotificationsMode.desktop.kt | 6 + .../views/usersettings/Appearance.desktop.kt | 70 +++++++++ .../usersettings/PrivacySettings.desktop.kt | 17 +++ .../ScanProtocolServer.desktop.kt | 9 ++ .../usersettings/SettingsView.desktop.kt | 21 +++ .../desktop/src/jvmMain/kotlin/Main.kt | 8 ++ 251 files changed, 4290 insertions(+), 2469 deletions(-) rename apps/multiplatform/android/src/main/java/chat/simplex/app/{views/helpers => }/MessagesFetcherWorker.kt (95%) delete mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt delete mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt delete mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt delete mode 100644 apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/helpers/Extensions.kt => common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt} (66%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/call => common/src/androidMain/kotlin/chat/simplex/common/helpers}/SoundPlayer.kt (60%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/platform/AppCommon.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt} (71%) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Back.android.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/Cryptor.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/Cryptor.android.kt} (83%) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/platform/Images.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt} (56%) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Log.android.kt create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Notifications.android.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt} (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt} (86%) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/platform/Share.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt} (58%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/platform/UI.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt} (66%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/VideoPlayer.kt => common/src/androidMain/kotlin/chat/simplex/common/platform/VideoPlayer.android.kt} (73%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/ui/theme/Type.kt => common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Type.android.kt} (81%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/call/CallView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt} (94%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/chat/ComposeView.android.kt} (74%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.android.kt} (68%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/chat/SendMsgView.android.kt} (77%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt} (57%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt} (56%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt} (67%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.android.kt} (75%) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.android.kt create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt} (92%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt => common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt} (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/Util.kt => common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt} (77%) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCode.android.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt => common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt} (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt} (74%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt => common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.android.kt} (67%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt => common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt} (61%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt => common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.android.kt} (74%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt => common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt} (72%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt => common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt} (77%) rename apps/multiplatform/{android/src/main => common/src/androidMain}/res/drawable/edit_text_cursor.xml (100%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/App.kt (77%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/AppLock.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/model/ChatModel.kt (98%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/model/SimpleXAPI.kt (98%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Back.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/platform/Backend.common.kt => common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt} (81%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Cryptor.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Log.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Notifications.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/UI.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/VideoPlayer.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/ui/theme/Color.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/ui/theme/Shape.kt (87%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/ui/theme/Theme.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/ui/theme/ThemeManager.kt (92%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt => common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt} (92%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/Preview.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/SplashView.kt (94%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/TerminalView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/WelcomeView.kt (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/call/CallManager.kt (83%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/call/IncomingCallAlertView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/call/WebRTC.kt (98%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ChatInfoView.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ChatItemInfoView.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ChatView.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ComposeFileView.kt (92%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ComposeImageView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt} (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ComposeVoiceView.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ContactPreferences.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/ContextItemView.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt} (76%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt} (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/VerifyCodeView.kt (90%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/group/AddGroupMembersView.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/group/GroupChatInfoView.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/group/GroupLinkView.kt (92%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/group/GroupMemberInfoView.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/group/GroupPreferences.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/group/GroupProfileView.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/group/WelcomeMessageView.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CICallItemView.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIChatFeatureView.kt (88%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIEventView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIFeaturePreferenceView.kt (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIFileView.kt (83%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIGroupInvitationView.kt (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt} (84%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIInvalidJSONView.kt (74%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIMetaView.kt (94%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIRcvDecryptionError.kt (81%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt} (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/CIVoiceView.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt} (94%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/DeletedItemView.kt (86%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/EmojiItemView.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/FramedItemView.kt (98%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt} (79%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/IntegrityErrorItemView.kt (86%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/MarkedDeletedItemView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chat/item/TextItemView.kt (87%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ChatHelpView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ChatListNavLinkView.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ChatListView.kt (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ChatPreviewView.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ContactConnectionView.kt (87%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ContactRequestView.kt (87%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ShareListNavLinkView.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/ShareListView.kt (92%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/chatlist/UserPicker.kt (90%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/database/ChatArchiveView.kt (57%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/database/DatabaseEncryptionView.kt (98%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/database/DatabaseErrorView.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/database/DatabaseView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/AlertManager.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/AnimationUtils.kt (87%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/ChatInfoImage.kt (87%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/ChooseAttachmentView.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/CloseSheetBar.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/CustomIcons.kt (99%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/CustomTimePicker.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/DataClasses.kt (82%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/DatabaseUtils.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/DefaultBasicTextField.kt (98%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt} (70%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/DefaultSwitch.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/DefaultTopAppBar.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/Enums.kt (61%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/ExposedDropDownSettingRow.kt (92%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/GestureDetector.kt (98%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GetImageView.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/LinkPreviews.kt (88%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt} (63%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/ModalView.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/Modifiers.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/SearchTextField.kt (98%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/Section.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/SimpleButton.kt (94%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/helpers/TextEditor.kt (87%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt} (65%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/localauth/LocalAuthView.kt (81%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/localauth/PasscodeView.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/localauth/PasswordEntry.kt (99%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/localauth/SetAppPasscodeView.kt (84%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/AddContactLearnMore.kt (74%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/AddContactView.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/AddGroupView.kt (86%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/ConnectViaLinkView.kt (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/ContactConnectionInfoView.kt (85%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/CreateLinkView.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/NewChatSheet.kt (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/newchat/PasteToConnect.kt (82%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt} (82%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt} (82%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/onboarding/CreateSimpleXAddress.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/onboarding/HowItWorks.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/onboarding/OnboardingView.kt (82%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt} (90%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/onboarding/SimpleXInfo.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/onboarding/WhatsNewView.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/AdvancedNetworkSettings.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt} (77%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/CallSettings.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/DeveloperView.kt (90%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/HelpView.kt (66%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/HiddenProfileView.kt (89%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/IncognitoView.kt (81%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/MarkdownHelpView.kt (84%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/NetworkAndServers.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/NotificationsSettingsView.kt (71%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/Preferences.kt (97%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt} (92%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/ProtocolServerView.kt (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/ProtocolServersView.kt (98%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/RTCServers.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt} (72%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt => common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt} (93%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/UserAddressLearnMore.kt (77%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/UserAddressView.kt (95%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/UserProfileView.kt (91%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/UserProfilesView.kt (96%) rename apps/multiplatform/{android/src/main/java/chat/simplex/app => common/src/commonMain/kotlin/chat/simplex/common}/views/usersettings/VersionInfoView.kt (55%) create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Back.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Cryptor.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Log.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Notifications.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/UI.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Type.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/SendMsgView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCode.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt create mode 100644 apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts index 5d54c5f7b6..a1b90e48d9 100644 --- a/apps/multiplatform/android/build.gradle.kts +++ b/apps/multiplatform/android/build.gradle.kts @@ -7,10 +7,6 @@ plugins { id("org.jetbrains.kotlin.plugin.serialization") } -repositories { - maven("https://jitpack.io") -} - android { compileSdkVersion(33) @@ -124,9 +120,16 @@ dependencies { //implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}") //implementation("androidx.compose.material:material:$compose_version") //implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") + implementation("androidx.appcompat:appcompat:1.5.1") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1") implementation("androidx.lifecycle:lifecycle-process:2.4.1") implementation("androidx.activity:activity-compose:1.5.0") + val work_version = "2.7.1" + implementation("androidx.work:work-runtime-ktx:$work_version") + implementation("androidx.work:work-multiprocess:$work_version") + + implementation("com.jakewharton:process-phoenix:2.1.2") + //implementation("androidx.compose.material:material-icons-extended:$compose_version") //implementation("androidx.compose.ui:ui-util:$compose_version") diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/BackupAgent.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/BackupAgent.kt index bbe4e8318c..417dfbbaa0 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/BackupAgent.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/BackupAgent.kt @@ -3,8 +3,8 @@ package chat.simplex.app import android.app.backup.BackupAgentHelper import android.app.backup.FullBackupDataOutput import android.content.Context -import chat.simplex.app.model.AppPreferences -import chat.simplex.app.model.AppPreferences.Companion.SHARED_PREFS_PRIVACY_FULL_BACKUP +import chat.simplex.common.model.AppPreferences +import chat.simplex.common.model.AppPreferences.Companion.SHARED_PREFS_PRIVACY_FULL_BACKUP class BackupAgent: BackupAgentHelper() { override fun onFullBackup(data: FullBackupDataOutput?) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 76604ac13a..63a67737b3 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -1,51 +1,41 @@ package chat.simplex.app -import android.app.Application import android.content.Intent import android.net.Uri import android.os.* -import android.os.SystemClock.elapsedRealtime -import android.util.Log import android.view.WindowManager import androidx.activity.compose.setContent -import androidx.activity.viewModels -import androidx.compose.animation.core.* -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.* -import chat.simplex.app.helpers.applyAppLocale -import chat.simplex.app.model.* +import chat.simplex.app.model.NtfManager import chat.simplex.app.model.NtfManager.getUserIdFromIntent -import chat.simplex.app.platform.mainActivity -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chatlist.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.* -import chat.simplex.app.views.onboarding.* -import chat.simplex.app.views.usersettings.* +import chat.simplex.common.* +import chat.simplex.common.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chatlist.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.* +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.* import java.lang.ref.WeakReference +import java.net.URI class MainActivity: FragmentActivity() { - private val vm by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - mainActivity = WeakReference(this) // testJson() - val m = vm.chatModel - applyAppLocale(m.controller.appPrefs.appLanguage) + mainActivity = WeakReference(this) + applyAppLocale(ChatModel.controller.appPrefs.appLanguage) // When call ended and orientation changes, it re-process old intent, it's unneeded. // Only needed to be processed on first creation of activity if (savedInstanceState == null) { - processNotificationIntent(intent, m) - processIntent(intent, m) - processExternalIntent(intent, m) + processNotificationIntent(intent) + processIntent(intent) + processExternalIntent(intent) } - if (m.controller.appPrefs.privacyProtectScreen.get()) { + if (ChatController.appPrefs.privacyProtectScreen.get()) { Log.d(TAG, "onCreate: set FLAG_SECURE") window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, @@ -54,17 +44,7 @@ class MainActivity: FragmentActivity() { } setContent { SimpleXTheme { - Surface(color = MaterialTheme.colors.background) { - MainPage( - m, - AppLock.userAuthorized, - AppLock.laFailed, - AppLock.destroyedAfterBackPress, - { AppLock.runAuthenticate() }, - { AppLock.setPerformLA(it) }, - showLANotice = { AppLock.showLANotice(m.controller.appPrefs.laNoticeShown) } - ) - } + AppScreen() } } SimplexApp.context.schedulePeriodicServiceRestartWorker() @@ -73,22 +53,13 @@ class MainActivity: FragmentActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - processIntent(intent, vm.chatModel) - processExternalIntent(intent, vm.chatModel) + processIntent(intent) + processExternalIntent(intent) } override fun onResume() { super.onResume() - val enteredBackgroundVal = AppLock.enteredBackground.value - val delay = vm.chatModel.controller.appPrefs.laLockDelay.get() - if (enteredBackgroundVal == null || elapsedRealtime() - enteredBackgroundVal >= delay * 1000) { - if (AppLock.userAuthorized.value != false) { - /** [runAuthenticate] will be called in [MainPage] if needed. Making like this prevents double showing of passcode on start */ - AppLock.setAuthState() - } else if (!vm.chatModel.activeCallViewIsVisible.value) { - AppLock.runAuthenticate() - } - } + AppLock.recheckAuthState() } override fun onPause() { @@ -98,13 +69,13 @@ class MainActivity: FragmentActivity() { * recreation but receives onStop after recreation. So using both (onPause and onStop) to prevent * unwanted multiple auth dialogs from [runAuthenticate] * */ - AppLock.enteredBackground.value = elapsedRealtime() + AppLock.appWasHidden() } override fun onStop() { super.onStop() VideoPlayer.stopAll() - AppLock.enteredBackground.value = elapsedRealtime() + AppLock.appWasHidden() } override fun onBackPressed() { @@ -117,7 +88,7 @@ class MainActivity: FragmentActivity() { super.onBackPressed() } - if (!onBackPressedDispatcher.hasEnabledCallbacks() && vm.chatModel.controller.appPrefs.performLA.get()) { + if (!onBackPressedDispatcher.hasEnabledCallbacks() && ChatController.appPrefs.performLA.get()) { // When pressed Back and there is no one wants to process the back event, clear auth state to force re-auth on launch AppLock.clearAuthState() AppLock.laFailed.value = true @@ -130,12 +101,7 @@ class MainActivity: FragmentActivity() { } } -class SimplexViewModel(application: Application): AndroidViewModel(application) { - val app = getApplication() - val chatModel = app.chatModel -} - -fun processNotificationIntent(intent: Intent?, chatModel: ChatModel) { +fun processNotificationIntent(intent: Intent?) { val userId = getUserIdFromIntent(intent) when (intent?.action) { NtfManager.OpenChatAction -> { @@ -179,16 +145,16 @@ fun processNotificationIntent(intent: Intent?, chatModel: ChatModel) { } } -fun processIntent(intent: Intent?, chatModel: ChatModel) { +fun processIntent(intent: Intent?) { when (intent?.action) { "android.intent.action.VIEW" -> { val uri = intent.data - if (uri != null) connectIfOpenedViaUri(uri, chatModel) + if (uri != null) connectIfOpenedViaUri(uri.toURI(), ChatModel) } } } -fun processExternalIntent(intent: Intent?, chatModel: ChatModel) { +fun processExternalIntent(intent: Intent?) { when (intent?.action) { Intent.ACTION_SEND -> { // Close active chat and show a list of chats @@ -204,13 +170,13 @@ fun processExternalIntent(intent: Intent?, chatModel: ChatModel) { isMediaIntent(intent) -> { val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri if (uri != null) { - chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", listOf(uri)) + chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", listOf(uri.toURI())) } // All other mime types } else -> { val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri if (uri != null) { - chatModel.sharedContent.value = SharedContent.File(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uri) + chatModel.sharedContent.value = SharedContent.File(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uri.toURI()) } } } @@ -224,7 +190,7 @@ fun processExternalIntent(intent: Intent?, chatModel: ChatModel) { isMediaIntent(intent) -> { val uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) as? List if (uris != null) { - chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uris) + chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uris.map { it.toURI() }) } // All other mime types } else -> {} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/MessagesFetcherWorker.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/MessagesFetcherWorker.kt rename to apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt index 6b43cd603a..3b8902b58c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/MessagesFetcherWorker.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt @@ -1,11 +1,13 @@ -package chat.simplex.app.views.helpers +package chat.simplex.app import android.content.Context import android.util.Log import androidx.work.* import chat.simplex.app.* import chat.simplex.app.SimplexService.Companion.showPassphraseNotification -import chat.simplex.app.model.ChatController +import chat.simplex.common.model.ChatController +import chat.simplex.common.views.helpers.DBMigrationResult +import chat.simplex.app.BuildConfig import kotlinx.coroutines.* import java.util.Date import java.util.concurrent.TimeUnit 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 15720dcd09..af50e6b057 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 @@ -1,24 +1,23 @@ package chat.simplex.app import android.app.Application -import android.net.LocalServerSocket -import android.util.Log +import chat.simplex.common.platform.Log import androidx.lifecycle.* import androidx.work.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.* -import chat.simplex.app.ui.theme.DefaultTheme -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.OnboardingStage +import chat.simplex.app.model.NtfManager +import chat.simplex.common.helpers.APPLICATION_ID +import chat.simplex.common.helpers.requiresIgnoringBattery +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.common.platform.* +import chat.simplex.common.views.call.RcvCallInvitation import com.jakewharton.processphoenix.ProcessPhoenix import kotlinx.coroutines.* -import kotlinx.serialization.decodeFromString import java.io.* -import java.lang.ref.WeakReference import java.util.* -import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit -import kotlin.concurrent.thread const val TAG = "SIMPLEX" @@ -26,6 +25,8 @@ class SimplexApp: Application(), LifecycleEventObserver { val chatModel: ChatModel get() = chatController.chatModel + val chatController: ChatController = ChatController + override fun onCreate() { super.onCreate() if (ProcessPhoenix.isPhoenixProcess(this)) { @@ -33,7 +34,8 @@ class SimplexApp: Application(), LifecycleEventObserver { } context = this initHaskell() - context.getDir("temp", MODE_PRIVATE).deleteRecursively() + initMultiplatform() + tmpDir.deleteRecursively() withBGApi { initChatController() runMigrations() @@ -77,7 +79,7 @@ class SimplexApp: Application(), LifecycleEventObserver { * */ if (chatModel.chatRunning.value != false && chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete && - appPreferences.notificationsMode.get() == NotificationsMode.SERVICE.name + appPrefs.notificationsMode.get() == NotificationsMode.SERVICE ) { SimplexService.start() } @@ -88,12 +90,12 @@ class SimplexApp: Application(), LifecycleEventObserver { } fun allowToStartServiceAfterAppExit() = with(chatModel.controller) { - appPrefs.notificationsMode.get() == NotificationsMode.SERVICE.name && + appPrefs.notificationsMode.get() == NotificationsMode.SERVICE && (!NotificationsMode.SERVICE.requiresIgnoringBattery || SimplexService.isIgnoringBatteryOptimizations()) } private fun allowToStartPeriodically() = with(chatModel.controller) { - appPrefs.notificationsMode.get() == NotificationsMode.PERIODIC.name && + appPrefs.notificationsMode.get() == NotificationsMode.PERIODIC && (!NotificationsMode.PERIODIC.requiresIgnoringBattery || SimplexService.isIgnoringBatteryOptimizations()) } @@ -131,4 +133,73 @@ class SimplexApp: Application(), LifecycleEventObserver { companion object { lateinit var context: SimplexApp private set } + + private fun initMultiplatform() { + androidAppContext = this + APPLICATION_ID = BuildConfig.APPLICATION_ID + ntfManager = object : chat.simplex.common.platform.NtfManager() { + override fun notifyContactConnected(user: User, contact: Contact) = NtfManager.notifyContactConnected(user, contact) + override fun notifyContactRequestReceived(user: User, cInfo: ChatInfo.ContactRequest) = NtfManager.notifyContactRequestReceived(user, cInfo) + override fun notifyMessageReceived(user: User, cInfo: ChatInfo, cItem: ChatItem) = NtfManager.notifyMessageReceived(user, cInfo, cItem) + override fun notifyCallInvitation(invitation: RcvCallInvitation) = NtfManager.notifyCallInvitation(invitation) + override fun hasNotificationsForChat(chatId: String): Boolean = NtfManager.hasNotificationsForChat(chatId) + override fun cancelNotificationsForChat(chatId: String) = NtfManager.cancelNotificationsForChat(chatId) + override fun displayNotification(user: User, chatId: String, displayName: String, msgText: String, image: String?, actions: List) = NtfManager.displayNotification(user, chatId, displayName, msgText, image, actions) + override fun createNtfChannelsMaybeShowAlert() = NtfManager.createNtfChannelsMaybeShowAlert() + override fun cancelCallNotification() = NtfManager.cancelCallNotification() + override fun cancelAllNotifications() = NtfManager.cancelAllNotifications() + } + platform = object : PlatformInterface { + override suspend fun androidServiceStart() { + SimplexService.start() + } + + override fun androidServiceSafeStop() { + SimplexService.safeStopService() + } + + override fun androidNotificationsModeChanged(mode: NotificationsMode) { + if (mode.requiresIgnoringBattery && !SimplexService.isIgnoringBatteryOptimizations()) { + appPrefs.backgroundServiceNoticeShown.set(false) + } + SimplexService.StartReceiver.toggleReceiver(mode == NotificationsMode.SERVICE) + CoroutineScope(Dispatchers.Default).launch { + if (mode == NotificationsMode.SERVICE) + SimplexService.start() + else + SimplexService.safeStopService() + } + + if (mode != NotificationsMode.PERIODIC) { + MessagesFetcherWorker.cancelAll() + } + SimplexService.showBackgroundServiceNoticeIfNeeded() + } + + override fun androidChatStartedAfterBeingOff() { + SimplexService.cancelPassphraseNotification() + when (appPrefs.notificationsMode.get()) { + NotificationsMode.SERVICE -> CoroutineScope(Dispatchers.Default).launch { platform.androidServiceStart() } + NotificationsMode.PERIODIC -> SimplexApp.context.schedulePeriodicWakeUp() + NotificationsMode.OFF -> {} + } + } + + override fun androidChatStopped() { + SimplexService.safeStopService() + MessagesFetcherWorker.cancelAll() + } + + override fun androidChatInitializedAndStarted() { + // Prevents from showing "Enable notifications" alert when onboarding wasn't complete yet + if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) { + SimplexService.showBackgroundServiceNoticeIfNeeded() + if (appPrefs.notificationsMode.get() == NotificationsMode.SERVICE) + withBGApi { + platform.androidServiceStart() + } + } + } + } + } } 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 135ab0b664..8624daa5e0 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 @@ -7,21 +7,26 @@ import android.content.pm.PackageManager import android.net.Uri import android.os.* import android.provider.Settings -import android.util.Log import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import chat.simplex.common.platform.Log import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.work.* -import chat.simplex.app.model.* -import chat.simplex.app.views.helpers.* +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 +import chat.simplex.common.platform.androidAppContext +import chat.simplex.common.views.helpers.* +import kotlinx.coroutines.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.* // based on: // https://robertohuertas.com/2019/06/29/android_foreground_services/ @@ -103,7 +108,7 @@ class SimplexService: Service() { if (chatDbStatus != DBMigrationResult.OK) { Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus") showPassphraseNotification(chatDbStatus) - safeStopService(self) + safeStopService() return@withApi } saveServiceState(self, ServiceState.STARTED) @@ -263,9 +268,9 @@ class SimplexService: Service() { * If there is a need to stop the service, use this function only. It makes sure that the service will be stopped without an * exception related to foreground services lifecycle * */ - fun safeStopService(context: Context) { + fun safeStopService() { if (isServiceStarted) { - context.stopService(Intent(context, SimplexService::class.java)) + androidAppContext.stopService(Intent(androidAppContext, SimplexService::class.java)) } else { stopAfterStart = true } @@ -274,9 +279,9 @@ class SimplexService: Service() { private suspend fun serviceAction(action: Action) { Log.d(TAG, "SimplexService serviceAction: ${action.name}") withContext(Dispatchers.IO) { - Intent(SimplexApp.context, SimplexService::class.java).also { + Intent(androidAppContext, SimplexService::class.java).also { it.action = action.name - ContextCompat.startForegroundService(SimplexApp.context, it) + ContextCompat.startForegroundService(androidAppContext, it) } } } @@ -350,7 +355,7 @@ class SimplexService: Service() { fun showBackgroundServiceNoticeIfNeeded() { val appPrefs = ChatController.appPrefs - val mode = NotificationsMode.valueOf(appPrefs.notificationsMode.get()!!) + val mode = appPrefs.notificationsMode.get() Log.d(TAG, "showBackgroundServiceNoticeIfNeeded") // Nothing to do if mode is OFF. Can be selected on on-boarding stage if (mode == NotificationsMode.OFF) return @@ -371,11 +376,10 @@ class SimplexService: Service() { 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.name) - ChatModel.notificationsMode.value = NotificationsMode.OFF - SimplexService.StartReceiver.toggleReceiver(false) + appPrefs.notificationsMode.set(NotificationsMode.OFF) + StartReceiver.toggleReceiver(false) MessagesFetcherWorker.cancelAll() - SimplexService.safeStopService(SimplexApp.context) + safeStopService() } else { // show battery optimization notice showBGServiceNoticeIgnoreOptimization(mode) @@ -487,18 +491,18 @@ class SimplexService: Service() { } fun isIgnoringBatteryOptimizations(): Boolean { - val powerManager = SimplexApp.context.getSystemService(Application.POWER_SERVICE) as PowerManager - return powerManager.isIgnoringBatteryOptimizations(SimplexApp.context.packageName) + val powerManager = androidAppContext.getSystemService(Application.POWER_SERVICE) as PowerManager + return powerManager.isIgnoringBatteryOptimizations(androidAppContext.packageName) } private fun askAboutIgnoringBatteryOptimization() { Intent().apply { @SuppressLint("BatteryLife") action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:${SimplexApp.context.packageName}") + 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) - SimplexApp.context.startActivity(this) + androidAppContext.startActivity(this) } } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt index 031a544260..5752d3fd6b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.kt @@ -1,6 +1,5 @@ package chat.simplex.app.model -import android.Manifest import android.app.* import android.app.TaskStackBuilder import android.content.* @@ -9,18 +8,21 @@ import android.graphics.BitmapFactory import android.hardware.display.DisplayManager import android.media.AudioAttributes import android.net.Uri -import android.util.Log import android.view.Display +import androidx.compose.ui.graphics.asAndroidBitmap import androidx.core.app.* import chat.simplex.app.* -import chat.simplex.app.platform.base64ToBitmap -import chat.simplex.app.platform.isAppOnForeground -import chat.simplex.app.views.call.* -import chat.simplex.app.views.chatlist.acceptContactRequest -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.NotificationPreviewMode -import chat.simplex.res.MR +import chat.simplex.app.TAG +import chat.simplex.app.views.call.IncomingCallActivity +import chat.simplex.app.views.call.getKeyguardManager +import chat.simplex.common.views.chatlist.acceptContactRequest +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.views.call.CallMediaType +import chat.simplex.common.views.call.RcvCallInvitation import kotlinx.datetime.Clock +import chat.simplex.res.MR object NtfManager { const val MessageChannel: String = "chat.simplex.app.MESSAGE_NOTIFICATION" @@ -35,7 +37,7 @@ object NtfManager { const val CallNotificationId: Int = -1 private const val UserIdKey: String = "userId" private const val ChatIdKey: String = "chatId" - private val appPreferences: AppPreferences by lazy { ChatController.appPrefs } + private val appPreferences: AppPreferences = ChatController.appPrefs private val context: Context get() = SimplexApp.context @@ -44,7 +46,7 @@ object NtfManager { return if (userId == -1L || userId == null) null else userId } - private val manager: NotificationManager = SimplexApp.context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager private var prevNtfTime = mutableMapOf() private val msgNtfTimeoutMs = 30000L @@ -52,10 +54,6 @@ object NtfManager { if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert() } - enum class NotificationAction { - ACCEPT_CONTACT_REQUEST - } - private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel { val callChannel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH) val attrs = AudioAttributes.Builder() @@ -121,7 +119,7 @@ object NtfManager { val largeIcon = when { actions.isEmpty() -> null image == null || previewMode == NotificationPreviewMode.HIDDEN.name -> BitmapFactory.decodeResource(context.resources, R.drawable.icon) - else -> base64ToBitmap(image) + else -> base64ToBitmap(image).asAndroidBitmap() } val builder = NotificationCompat.Builder(context, MessageChannel) .setContentTitle(title) @@ -160,7 +158,7 @@ object NtfManager { with(NotificationManagerCompat.from(context)) { // using cInfo.id only shows one notification per chat and updates it when the message arrives - if (ActivityCompat.checkSelfPermission(SimplexApp.context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { + if (ActivityCompat.checkSelfPermission(SimplexApp.context, android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { notify(chatId.hashCode(), builder.build()) notify(0, summary) } @@ -214,7 +212,7 @@ object NtfManager { val largeIcon = if (image == null || previewMode == NotificationPreviewMode.HIDDEN.name) BitmapFactory.decodeResource(context.resources, R.drawable.icon) else - base64ToBitmap(image) + base64ToBitmap(image).asAndroidBitmap() ntfBuilder = ntfBuilder .setContentTitle(title) @@ -229,7 +227,7 @@ object NtfManager { // This makes notification sound and vibration repeat endlessly notification.flags = notification.flags or NotificationCompat.FLAG_INSISTENT with(NotificationManagerCompat.from(context)) { - if (ActivityCompat.checkSelfPermission(SimplexApp.context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { + if (ActivityCompat.checkSelfPermission(SimplexApp.context, android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { notify(CallNotificationId, notification) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt deleted file mode 100644 index 4d89009642..0000000000 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.common.kt +++ /dev/null @@ -1,30 +0,0 @@ -package chat.simplex.app.platform - -import chat.simplex.app.BuildConfig -import chat.simplex.app.model.ChatController -import chat.simplex.app.ui.theme.DefaultTheme -import java.util.* - -class FifoQueue(private var capacity: Int) : LinkedList() { - override fun add(element: E): Boolean { - if(size > capacity) removeFirst() - return super.add(element) - } -} - -fun runMigrations() { - val lastMigration = ChatController.appPrefs.lastMigratedVersionCode - if (lastMigration.get() < BuildConfig.VERSION_CODE) { - while (true) { - if (lastMigration.get() < 117) { - if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.DARK.name) { - ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name) - } - lastMigration.set(117) - } else { - lastMigration.set(BuildConfig.VERSION_CODE) - break - } - } - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt deleted file mode 100644 index 7d3c592ab4..0000000000 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Files.common.kt +++ /dev/null @@ -1,30 +0,0 @@ -package chat.simplex.app.platform - -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.CIFile -import java.io.File - -fun getFilesDirectory(): String { - return SimplexApp.context.filesDir.toString() -} - -fun getTempFilesDirectory(): String { - return "${getFilesDirectory()}/temp_files" -} - -fun getAppFilesDirectory(): String { - return "${getFilesDirectory()}/app_files" -} - -fun getAppFilePath(fileName: String): String { - return "${getAppFilesDirectory()}/$fileName" -} - -fun getLoadedFilePath(file: CIFile?): String? { - return if (file?.filePath != null && file.loaded) { - val filePath = getAppFilePath(file.filePath) - if (File(filePath).exists()) filePath else null - } else { - null - } -} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt deleted file mode 100644 index c0065b3e23..0000000000 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Resources.kt +++ /dev/null @@ -1,9 +0,0 @@ -package chat.simplex.app.platform - -import android.app.UiModeManager -import android.content.Context -import chat.simplex.app.SimplexApp - -// Non-@Composable implementation -fun isInNightMode() = - (SimplexApp.context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager).nightMode == UiModeManager.MODE_NIGHT_YES diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt index d2ffc507e0..104b654c58 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt @@ -4,16 +4,14 @@ import android.app.Activity import android.app.KeyguardManager import android.content.Context import android.content.Intent -import android.content.res.Configuration import android.os.Build import android.os.Bundle -import android.util.Log +import chat.simplex.common.platform.Log import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.viewModels +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -24,27 +22,27 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.* import chat.simplex.app.R -import chat.simplex.app.model.* +import chat.simplex.common.model.* import chat.simplex.app.model.NtfManager.OpenChatAction -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.ProfileImage +import chat.simplex.common.platform.ntfManager +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.call.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.stringResource import kotlinx.datetime.Clock class IncomingCallActivity: ComponentActivity() { - private val vm by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { IncomingCallActivityView(vm.chatModel) } + setContent { IncomingCallActivityView(ChatModel) } unlockForIncomingCall() } @@ -103,7 +101,7 @@ fun IncomingCallActivityView(m: ChatModel) { ) { if (showCallView) { Box { - ActiveCallView(m) + ActiveCallView() if (invitation != null) IncomingCallAlertView(invitation, m) } } else if (invitation != null) { @@ -121,7 +119,7 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo DisposableEffect(Unit) { onDispose { // Cancel notification whatever happens next since otherwise sound from notification and from inside the app can co-exist - chatModel.controller.ntfManager.cancelCallNotification() + ntfManager.cancelCallNotification() } } IncomingCallLockScreenAlertLayout( @@ -131,7 +129,7 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo rejectCall = { cm.endCall(invitation = invitation) }, ignoreCall = { chatModel.activeCallInvitation.value = null - chatModel.controller.ntfManager.cancelCallNotification() + ntfManager.cancelCallNotification() }, acceptCall = { cm.acceptIncomingCall(invitation = invitation) }, openApp = { @@ -171,18 +169,18 @@ fun IncomingCallLockScreenAlertLayout( Text(invitation.contact.chatViewName, style = MaterialTheme.typography.h2) Spacer(Modifier.fillMaxHeight().weight(1f)) Row { - LockScreenCallButton(stringResource(MR.strings.reject), painterResource(MR.images.ic_call_end_filled), Color.Red, rejectCall) + LockScreenCallButton(stringResource(MR.strings.reject), painterResource(R.drawable.ic_call_end_filled), Color.Red, rejectCall) Spacer(Modifier.size(48.dp)) - LockScreenCallButton(stringResource(MR.strings.ignore), painterResource(MR.images.ic_close), MaterialTheme.colors.primary, ignoreCall) + LockScreenCallButton(stringResource(MR.strings.ignore), painterResource(R.drawable.ic_close), MaterialTheme.colors.primary, ignoreCall) Spacer(Modifier.size(48.dp)) - LockScreenCallButton(stringResource(MR.strings.accept), painterResource(MR.images.ic_check_filled), SimplexGreen, acceptCall) + LockScreenCallButton(stringResource(MR.strings.accept), painterResource(R.drawable.ic_check_filled), SimplexGreen, acceptCall) } } else if (callOnLockScreen == CallOnLockScreen.SHOW) { SimpleXLogo() Text(stringResource(MR.strings.open_simplex_chat_to_accept_call), textAlign = TextAlign.Center, lineHeight = 22.sp) Text(stringResource(MR.strings.allow_accepting_calls_from_lock_screen), textAlign = TextAlign.Center, style = MaterialTheme.typography.body2, lineHeight = 22.sp) Spacer(Modifier.fillMaxHeight().weight(1f)) - SimpleButton(text = stringResource(MR.strings.open_verb), icon = painterResource(MR.images.ic_check_filled), click = openApp) + SimpleButton(text = stringResource(MR.strings.open_verb), icon = painterResource(R.drawable.ic_check_filled), click = openApp) } } } @@ -190,7 +188,7 @@ fun IncomingCallLockScreenAlertLayout( @Composable private fun SimpleXLogo() { Image( - painter = painterResource(if (isInDarkTheme()) MR.images.logo_light else MR.images.logo), + painter = painterResource(if (isInDarkTheme()) R.drawable.logo_light else R.drawable.logo), contentDescription = stringResource(MR.strings.image_descr_simplex_logo), modifier = Modifier .padding(vertical = DEFAULT_PADDING) @@ -219,10 +217,10 @@ private fun LockScreenCallButton(text: String, icon: Painter, color: Color, acti } } -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true -) +)*/ @Composable fun PreviewIncomingCallLockScreenAlert() { SimpleXTheme(true) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt deleted file mode 100644 index 7081acf028..0000000000 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.kt +++ /dev/null @@ -1,17 +0,0 @@ -package chat.simplex.app.views.newchat - -import android.graphics.Bitmap - -fun Bitmap.replaceColor(from: Int, to: Int): Bitmap { - val pixels = IntArray(width * height) - getPixels(pixels, 0, width, 0, 0, width, height) - var i = 0 - while (i < pixels.size) { - if (pixels[i] == from) { - pixels[i] = to - } - i++ - } - setPixels(pixels, 0, width, 0, 0, width, height) - return this -} diff --git a/apps/multiplatform/build.gradle.kts b/apps/multiplatform/build.gradle.kts index 94cecd1725..4e7ac583a5 100644 --- a/apps/multiplatform/build.gradle.kts +++ b/apps/multiplatform/build.gradle.kts @@ -55,7 +55,6 @@ allprojects { google() mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - maven("https://jitpack.io") } } diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 092f87690b..5b45a2317f 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -31,7 +31,6 @@ kotlin { } val commonMain by getting { - kotlin.srcDir("./build/generated/moko/commonMain/src/") dependencies { api(compose.runtime) api(compose.foundation) @@ -57,49 +56,39 @@ kotlin { implementation(kotlin("test")) } } - // LALAL CHANGE TO IMPLEMENTATION val androidMain by getting { - kotlin.srcDir("./build/generated/moko/commonMain/src/") dependencies { - api("androidx.appcompat:appcompat:1.5.1") - api("androidx.core:core-ktx:1.9.0") - api("androidx.activity:activity-compose:1.5.0") + implementation("androidx.activity:activity-compose:1.5.0") val work_version = "2.7.1" - api("androidx.work:work-runtime-ktx:$work_version") - api("androidx.work:work-multiprocess:$work_version") - api("com.google.accompanist:accompanist-insets:0.23.0") - api("dev.icerock.moko:resources:0.22.3") + implementation("androidx.work:work-runtime-ktx:$work_version") + implementation("com.google.accompanist:accompanist-insets:0.23.0") + implementation("dev.icerock.moko:resources:0.22.3") // Video support - api("com.google.android.exoplayer:exoplayer:2.17.1") + implementation("com.google.android.exoplayer:exoplayer:2.17.1") // Biometric authentication - api("androidx.biometric:biometric:1.2.0-alpha04") + implementation("androidx.biometric:biometric:1.2.0-alpha04") //Barcode - api("org.boofcv:boofcv-android:0.40.1") + implementation("org.boofcv:boofcv-android:0.40.1") //Camera Permission - api("com.google.accompanist:accompanist-permissions:0.23.0") + implementation("com.google.accompanist:accompanist-permissions:0.23.0") - api("androidx.webkit:webkit:1.4.0") + implementation("androidx.webkit:webkit:1.4.0") // GIFs support - api("io.coil-kt:coil-compose:2.1.0") - api("io.coil-kt:coil-gif:2.1.0") + implementation("io.coil-kt:coil-compose:2.1.0") + implementation("io.coil-kt:coil-gif:2.1.0") - api("com.jakewharton:process-phoenix:2.1.2") + implementation("com.jakewharton:process-phoenix:2.1.2") val camerax_version = "1.1.0-beta01" - api("androidx.camera:camera-core:${camerax_version}") - api("androidx.camera:camera-camera2:${camerax_version}") - api("androidx.camera:camera-lifecycle:${camerax_version}") - api("androidx.camera:camera-view:${camerax_version}") - - // LALAL REMOVE - api("org.jsoup:jsoup:1.13.1") - api("com.godaddy.android.colorpicker:compose-color-picker-jvm:0.7.0") - api("androidx.compose.ui:ui-tooling-preview:${extra["compose.version"]}") + implementation("androidx.camera:camera-core:${camerax_version}") + implementation("androidx.camera:camera-camera2:${camerax_version}") + implementation("androidx.camera:camera-lifecycle:${camerax_version}") + implementation("androidx.camera:camera-view:${camerax_version}") } } val desktopMain by getting { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt new file mode 100644 index 0000000000..e237272eb0 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt @@ -0,0 +1,22 @@ +package chat.simplex.common.helpers + +import android.net.Uri +import android.os.Build +import chat.simplex.common.model.NotificationsMode +import java.net.URI + +val NotificationsMode.requiresIgnoringBatterySinceSdk: Int get() = when(this) { + NotificationsMode.OFF -> Int.MAX_VALUE + NotificationsMode.PERIODIC -> Build.VERSION_CODES.M + NotificationsMode.SERVICE -> Build.VERSION_CODES.S + /*INSTANT -> Int.MAX_VALUE - for Firebase notifications */ +} + +val NotificationsMode.requiresIgnoringBattery + get() = requiresIgnoringBatterySinceSdk <= Build.VERSION.SDK_INT + +lateinit var APPLICATION_ID: String + +fun Uri.toURI(): URI = URI(toString()) + +fun URI.toUri(): Uri = Uri.parse(toString()) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/helpers/Extensions.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt similarity index 66% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/helpers/Extensions.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt index 61a1f2e836..5a644466a4 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/helpers/Extensions.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt @@ -1,22 +1,22 @@ -package chat.simplex.app.helpers +package chat.simplex.common.helpers import android.app.Activity import android.content.res.Configuration -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.SharedPreference -import chat.simplex.app.platform.defaultLocale +import chat.simplex.common.model.SharedPreference +import chat.simplex.common.platform.androidAppContext +import chat.simplex.common.platform.defaultLocale import java.util.* -fun saveAppLocale(pref: SharedPreference, activity: Activity, languageCode: String? = null) { +fun Activity.saveAppLocale(pref: SharedPreference, languageCode: String? = null) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // val localeManager = SimplexApp.context.getSystemService(LocaleManager::class.java) // localeManager.applicationLocales = LocaleList(Locale.forLanguageTag(languageCode ?: return)) // } else { pref.set(languageCode) if (languageCode == null) { - activity.applyLocale(defaultLocale) + applyLocale(defaultLocale) } - activity.recreate() + recreate() // } } @@ -30,10 +30,10 @@ fun Activity.applyAppLocale(pref: SharedPreference) { private fun Activity.applyLocale(locale: Locale) { Locale.setDefault(locale) - val appConf = Configuration(SimplexApp.context.resources.configuration).apply { setLocale(locale) } + val appConf = Configuration(androidAppContext.resources.configuration).apply { setLocale(locale) } val activityConf = Configuration(resources.configuration).apply { setLocale(locale) } @Suppress("DEPRECATION") - SimplexApp.context.resources.updateConfiguration(appConf, resources.displayMetrics) + androidAppContext.resources.updateConfiguration(appConf, resources.displayMetrics) @Suppress("DEPRECATION") resources.updateConfiguration(activityConf, resources.displayMetrics) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/SoundPlayer.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/SoundPlayer.kt similarity index 60% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/SoundPlayer.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/SoundPlayer.kt index 314032984a..25369ffdfd 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/SoundPlayer.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/SoundPlayer.kt @@ -1,22 +1,22 @@ -package chat.simplex.app.views.call +package chat.simplex.common.helpers -import android.content.Context import android.media.* import android.net.Uri import android.os.VibrationEffect import android.os.Vibrator import androidx.core.content.ContextCompat -import chat.simplex.app.R -import chat.simplex.app.SimplexApp -import chat.simplex.app.views.helpers.withScope +import chat.simplex.common.R +import chat.simplex.common.platform.SoundPlayerInterface +import chat.simplex.common.platform.androidAppContext +import chat.simplex.common.views.helpers.withScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -class SoundPlayer { +object SoundPlayer: SoundPlayerInterface { private var player: MediaPlayer? = null var playing = false - fun start(scope: CoroutineScope, sound: Boolean) { + override fun start(scope: CoroutineScope, sound: Boolean) { player?.reset() player = MediaPlayer().apply { setAudioAttributes( @@ -25,10 +25,10 @@ class SoundPlayer { .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .build() ) - setDataSource(SimplexApp.context, Uri.parse("android.resource://" + SimplexApp.context.packageName + "/" + R.raw.ring_once)) + setDataSource(androidAppContext, Uri.parse("android.resource://" + androidAppContext.packageName + "/" + R.raw.ring_once)) prepare() } - val vibrator = ContextCompat.getSystemService(SimplexApp.context, Vibrator::class.java) + val vibrator = ContextCompat.getSystemService(androidAppContext, Vibrator::class.java) val effect = VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE) playing = true withScope(scope) { @@ -40,12 +40,8 @@ class SoundPlayer { } } - fun stop() { + override fun stop() { playing = false player?.stop() } - - companion object { - val shared = SoundPlayer() - } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt similarity index 71% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index 4fc20c504b..39b62a1c8b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -1,23 +1,32 @@ -package chat.simplex.app.platform +package chat.simplex.common.platform +import android.annotation.SuppressLint +import android.content.Context import android.net.LocalServerSocket import android.util.Log -import chat.simplex.app.* +import androidx.fragment.app.FragmentActivity +import chat.simplex.common.* +import chat.simplex.common.platform.* import java.io.* import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.Semaphore import kotlin.concurrent.thread +import kotlin.random.Random + +actual val appPlatform = AppPlatform.ANDROID var isAppOnForeground: Boolean = false @Suppress("ConstantLocale") val defaultLocale: Locale = Locale.getDefault() -var mainActivity: WeakReference = WeakReference(null) +@SuppressLint("StaticFieldLeak") +lateinit var androidAppContext: Context +lateinit var mainActivity: WeakReference -fun initHaskell() { - val socketName = BuildConfig.APPLICATION_ID + ".local.socket.address.listen.native.cmd2" +actual fun initHaskell() { + val socketName = "chat.simplex.app.local.socket.address.listen.native.cmd2" + Random.nextLong(100000) val s = Semaphore(0) thread(name="stdout/stderr pipe") { Log.d(TAG, "starting server") diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Back.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Back.android.kt new file mode 100644 index 0000000000..dfaeac695e --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Back.android.kt @@ -0,0 +1,9 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.* + +@SuppressWarnings("MissingJvmstatic") +@Composable +actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) { + androidx.activity.compose.BackHandler(enabled, onBack) +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Cryptor.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Cryptor.android.kt similarity index 83% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Cryptor.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Cryptor.android.kt index 1099b2fb2d..dc6c53ecbc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Cryptor.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Cryptor.android.kt @@ -1,24 +1,23 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.platform import android.annotation.SuppressLint import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties -import android.util.Log -import chat.simplex.app.R -import chat.simplex.app.TAG -import chat.simplex.app.views.helpers.AlertManager -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.views.helpers.AlertManager +import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR import java.security.KeyStore import javax.crypto.* import javax.crypto.spec.GCMParameterSpec +actual val cryptor: CryptorInterface = Cryptor() + @SuppressLint("ObsoleteSdkInt") -internal class Cryptor { +internal class Cryptor: CryptorInterface { private var keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } private var warningShown = false - fun decryptData(data: ByteArray, iv: ByteArray, alias: String): String? { + override fun decryptData(data: ByteArray, iv: ByteArray, alias: String): String? { val secretKey = getSecretKey(alias) if (secretKey == null) { if (!warningShown) { @@ -37,13 +36,13 @@ internal class Cryptor { return runCatching { String(cipher.doFinal(data))}.onFailure { Log.e(TAG, "doFinal: ${it.stackTraceToString()}") }.getOrNull() } - fun encryptText(text: String, alias: String): Pair { + override fun encryptText(text: String, alias: String): Pair { val cipher: Cipher = Cipher.getInstance(TRANSFORMATION) cipher.init(Cipher.ENCRYPT_MODE, createSecretKey(alias)) return Pair(cipher.doFinal(text.toByteArray(charset("UTF-8"))), cipher.iv) } - fun deleteKey(alias: String) { + override fun deleteKey(alias: String) { if (!keyStore.containsAlias(alias)) return keyStore.deleteEntry(alias) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt new file mode 100644 index 0000000000..522d861590 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -0,0 +1,40 @@ +package chat.simplex.common.platform + +import android.app.Application +import android.net.Uri +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import chat.simplex.common.helpers.toURI +import chat.simplex.common.helpers.toUri +import java.io.* +import java.net.URI + +actual val dataDir: File = androidAppContext.dataDir +actual val tmpDir: File = androidAppContext.getDir("temp", Application.MODE_PRIVATE) +actual val cacheDir: File = androidAppContext.cacheDir + +@Composable +actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher { + val launcher = rememberLauncherForActivityResult( + contract = if (getContent) ActivityResultContracts.GetContent() else ActivityResultContracts.CreateDocument(), + onResult = { onResult(it?.toURI()) } + ) + return FileChooserLauncher(launcher) +} + +actual class FileChooserLauncher actual constructor() { + private lateinit var launcher: ManagedActivityResultLauncher + + constructor(launcher: ManagedActivityResultLauncher): this() { + this.launcher = launcher + } + + actual suspend fun launch(input: String) { + launcher.launch(input) + } +} + +actual fun URI.inputStream(): InputStream? = androidAppContext.contentResolver.openInputStream(toUri()) +actual fun URI.outputStream(): OutputStream = androidAppContext.contentResolver.openOutputStream(toUri())!! diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Images.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt similarity index 56% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Images.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt index 7566046043..4140cd19a9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Images.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt @@ -1,36 +1,40 @@ -package chat.simplex.app.platform +package chat.simplex.common.platform import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.AnimatedImageDrawable -import android.net.Uri import android.os.Build import android.util.Base64 -import android.util.Log import android.webkit.MimeTypeMap +import androidx.compose.ui.graphics.* import androidx.core.graphics.applyCanvas import androidx.core.graphics.drawable.toBitmap -import chat.simplex.app.* -import chat.simplex.app.views.helpers.errorBitmap -import chat.simplex.app.views.helpers.getFileName +import androidx.core.graphics.scale +import boofcv.android.ConvertBitmap +import boofcv.struct.image.GrayU8 +import chat.simplex.common.R +import chat.simplex.common.views.helpers.errorBitmap +import chat.simplex.common.views.helpers.getFileName import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.net.URI import kotlin.math.min import kotlin.math.sqrt -fun base64ToBitmap(base64ImageString: String): Bitmap { +actual fun base64ToBitmap(base64ImageString: String): ImageBitmap { val imageString = base64ImageString .removePrefix("data:image/png;base64,") .removePrefix("data:image/jpg;base64,") - try { + return try { val imageBytes = Base64.decode(imageString, Base64.NO_WRAP) - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size).asImageBitmap() } catch (e: Exception) { Log.e(TAG, "base64ToBitmap error: $e") - return errorBitmap + errorBitmap.asImageBitmap() } } -fun resizeImageToStrSize(image: Bitmap, maxDataSize: Long): String { +actual fun resizeImageToStrSize(image: ImageBitmap, maxDataSize: Long): String { var img = image var str = compressImageStr(img) while (str.length > maxDataSize) { @@ -38,14 +42,14 @@ fun resizeImageToStrSize(image: Bitmap, maxDataSize: Long): String { val clippedRatio = min(ratio, 2.0) val width = (img.width.toDouble() / clippedRatio).toInt() val height = img.height * width / img.width - img = Bitmap.createScaledBitmap(img, width, height, true) + img = Bitmap.createScaledBitmap(img.asAndroidBitmap(), width, height, true).asImageBitmap() str = compressImageStr(img) } return str } // Inspired by https://github.com/MakeItEasyDev/Jetpack-Compose-Capture-Image-Or-Choose-from-Gallery -fun cropToSquare(image: Bitmap): Bitmap { +actual fun cropToSquare(image: ImageBitmap): ImageBitmap { var xOffset = 0 var yOffset = 0 val side = min(image.height, image.width) @@ -54,22 +58,22 @@ fun cropToSquare(image: Bitmap): Bitmap { } else { yOffset = (image.height - side) / 2 } - return Bitmap.createBitmap(image, xOffset, yOffset, side, side) + return Bitmap.createBitmap(image.asAndroidBitmap(), xOffset, yOffset, side, side).asImageBitmap() } -private fun compressImageStr(bitmap: Bitmap): String { - val usePng = bitmap.hasAlpha() +actual fun compressImageStr(bitmap: ImageBitmap): String { + val usePng = bitmap.hasAlpha val ext = if (usePng) "png" else "jpg" return "data:image/$ext;base64," + Base64.encodeToString(compressImageData(bitmap, usePng).toByteArray(), Base64.NO_WRAP) } -private fun compressImageData(bitmap: Bitmap, usePng: Boolean): ByteArrayOutputStream { +actual fun compressImageData(bitmap: ImageBitmap, usePng: Boolean): ByteArrayOutputStream { val stream = ByteArrayOutputStream() - bitmap.compress(if (!usePng) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG, 85, stream) + bitmap.asAndroidBitmap().compress(if (!usePng) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG, 85, stream) return stream } -fun resizeImageToDataSize(image: Bitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream { +actual fun resizeImageToDataSize(image: ImageBitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream { var img = image var stream = compressImageData(img, usePng) while (stream.size() > maxDataSize) { @@ -77,30 +81,36 @@ fun resizeImageToDataSize(image: Bitmap, usePng: Boolean, maxDataSize: Long): By val clippedRatio = min(ratio, 2.0) val width = (img.width.toDouble() / clippedRatio).toInt() val height = img.height * width / img.width - img = Bitmap.createScaledBitmap(img, width, height, true) + img = Bitmap.createScaledBitmap(img.asAndroidBitmap(), width, height, true).asImageBitmap() stream = compressImageData(img, usePng) } return stream } -fun Bitmap.addLogo(): Bitmap = applyCanvas { +actual fun GrayU8.toImageBitmap(): ImageBitmap = ConvertBitmap.grayToBitmap(this, Bitmap.Config.RGB_565).asImageBitmap() + +actual fun ImageBitmap.addLogo(): ImageBitmap = asAndroidBitmap().applyCanvas { val radius = (width * 0.16f) / 2 val paint = android.graphics.Paint() paint.color = android.graphics.Color.WHITE drawCircle(width / 2f, height / 2f, radius, paint) - val logo = SimplexApp.context.resources.getDrawable(R.mipmap.icon_foreground, null).toBitmap() + val logo = androidAppContext.resources.getDrawable(R.drawable.icon_foreground_android_common, null).toBitmap() val logoSize = (width * 0.24).toInt() translate((width - logoSize) / 2f, (height - logoSize) / 2f) drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null) -} +}.asImageBitmap() -fun isImage(uri: Uri): Boolean = +actual fun ImageBitmap.scale(width: Int, height: Int): ImageBitmap = asAndroidBitmap().scale(width, height).asImageBitmap() + +actual fun isImage(uri: URI): Boolean = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileName(uri)?.split(".")?.last())?.contains("image/") == true - -fun isAnimImage(uri: Uri, drawable: Any?): Boolean { +actual fun isAnimImage(uri: URI, drawable: Any?): Boolean { val isAnimNewApi = Build.VERSION.SDK_INT >= 28 && drawable is AnimatedImageDrawable val isAnimOldApi = Build.VERSION.SDK_INT < 28 && (getFileName(uri)?.endsWith(".gif") == true || getFileName(uri)?.endsWith(".webp") == true) return isAnimNewApi || isAnimOldApi -} \ No newline at end of file +} + +actual fun loadImageBitmap(inputStream: InputStream): ImageBitmap = + BitmapFactory.decodeStream(inputStream).asImageBitmap() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Log.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Log.android.kt new file mode 100644 index 0000000000..aca8efcb6f --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Log.android.kt @@ -0,0 +1,10 @@ +package chat.simplex.common.platform + +import android.util.Log + +actual object Log { + actual fun d(tag: String, text: String) = Log.d(tag, text).run{} + actual fun e(tag: String, text: String) = Log.e(tag, text).run{} + actual fun i(tag: String, text: String) = Log.i(tag, text).run{} + actual fun w(tag: String, text: String) = Log.w(tag, text).run{} +} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt new file mode 100644 index 0000000000..f4f748ef0f --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt @@ -0,0 +1,16 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.google.accompanist.insets.navigationBarsWithImePadding + +actual fun Modifier.navigationBarsWithImePadding(): Modifier = navigationBarsWithImePadding() + +@Composable +actual fun ProvideWindowInsets( + consumeWindowInsets: Boolean, + windowInsetsAnimationsEnabled: Boolean, + content: @Composable () -> Unit +) { + com.google.accompanist.insets.ProvideWindowInsets(content = content) +} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Notifications.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Notifications.android.kt new file mode 100644 index 0000000000..f0268219d2 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Notifications.android.kt @@ -0,0 +1,3 @@ +package chat.simplex.common.platform + +actual fun allowedToShowNotification(): Boolean = !isAppOnForeground diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index 11305db50a..e6c6ebad09 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PlatformTextField.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.platform +package chat.simplex.common.platform import android.annotation.SuppressLint import android.content.Context @@ -27,20 +27,22 @@ import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.InputConnectionCompat import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doOnTextChanged -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.CurrentColors -import chat.simplex.app.views.chat.* -import chat.simplex.app.views.helpers.SharedContent -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.* +import chat.simplex.common.R +import chat.simplex.common.helpers.toURI +import chat.simplex.common.model.ChatModel +import chat.simplex.common.ui.theme.CurrentColors +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.helpers.SharedContent +import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.delay import java.lang.reflect.Field +import java.net.URI @Composable -fun NativeKeyboard( +actual fun PlatformTextField( composeState: MutableState, textStyle: MutableState, showDeleteTextButton: MutableState, @@ -85,7 +87,7 @@ fun NativeKeyboard( } catch (e: Exception) { return@OnCommitContentListener false } - ChatModel.sharedContent.value = SharedContent.Media("", listOf(inputContentInfo.contentUri)) + ChatModel.sharedContent.value = SharedContent.Media("", listOf(inputContentInfo.contentUri.toURI())) true } return InputConnectionCompat.createWrapper(connection, editorInfo, onCommit) @@ -96,7 +98,7 @@ fun NativeKeyboard( editText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or editText.inputType editText.setTextColor(textColor.toArgb()) editText.textSize = textStyle.value.fontSize.value - val drawable = it.getDrawable(R.drawable.send_msg_view_background)!! + val drawable = androidAppContext.getDrawable(R.drawable.send_msg_view_background)!! DrawableCompat.setTint(drawable, tintColor.toArgb()) editText.background = drawable editText.setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom) @@ -134,7 +136,7 @@ fun NativeKeyboard( } if (showKeyboard) { it.requestFocus() - val imm: InputMethodManager = SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + val imm: InputMethodManager = androidAppContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT) showKeyboard = false } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt similarity index 86% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt index 01387dd963..c24ade47d9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.platform import android.app.Application import android.content.Context @@ -7,34 +7,22 @@ import android.media.AudioManager.AudioPlaybackCallback import android.media.MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED import android.media.MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED import android.os.Build -import android.util.Log import androidx.compose.runtime.* -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.model.ChatItem -import chat.simplex.app.views.helpers.AudioPlayer.duration import chat.simplex.res.MR +import chat.simplex.common.model.ChatItem +import chat.simplex.common.platform.AudioPlayer.duration +import chat.simplex.common.views.helpers.* import kotlinx.coroutines.* import java.io.* -interface Recorder { - fun start(onProgressUpdate: (position: Int?, finished: Boolean) -> Unit): String - fun stop(): Int -} - -class RecorderNative(): Recorder { - companion object { - // Allows to stop the recorder from outside without having the recorder in a variable - var stopRecording: (() -> Unit)? = null - const val extension = "m4a" - } +actual class RecorderNative: RecorderInterface { private var recorder: MediaRecorder? = null private var progressJob: Job? = null private var filePath: String? = null private var recStartedAt: Long? = null private fun initRecorder() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - MediaRecorder(SimplexApp.context) + MediaRecorder(androidAppContext) } else { MediaRecorder() } @@ -51,8 +39,7 @@ class RecorderNative(): Recorder { rec.setAudioSamplingRate(16000) rec.setAudioEncodingBitRate(32000) rec.setMaxDuration(MAX_VOICE_MILLIS_FOR_SENDING) - val tmpDir = SimplexApp.context.getDir("temp", Application.MODE_PRIVATE) - val fileToSave = File.createTempFile(generateNewFileName("voice", "${extension}_"), ".tmp", tmpDir) + val fileToSave = File.createTempFile(generateNewFileName("voice", "${RecorderInterface.extension}_"), ".tmp", tmpDir) fileToSave.deleteOnExit() val path = fileToSave.absolutePath filePath = path @@ -75,13 +62,13 @@ class RecorderNative(): Recorder { stop() } } - stopRecording = { stop() } + RecorderInterface.stopRecording = { stop() } return path } override fun stop(): Int { val path = filePath ?: return 0 - stopRecording = null + RecorderInterface.stopRecording = null runCatching { recorder?.stop() } @@ -110,7 +97,7 @@ class RecorderNative(): Recorder { private fun realDuration(path: String): Int? = duration(path) ?: progress() } -object AudioPlayer { +actual object AudioPlayer: AudioPlayerInterface { private val player = MediaPlayer().apply { setAudioAttributes( AudioAttributes.Builder() @@ -118,13 +105,13 @@ object AudioPlayer { .setUsage(AudioAttributes.USAGE_MEDIA) .build() ) - (SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager) + (androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager) .registerAudioPlaybackCallback(object: AudioPlaybackCallback() { override fun onPlaybackConfigChanged(configs: MutableList?) { if (configs?.any { it.audioAttributes.usage == AudioAttributes.USAGE_VOICE_COMMUNICATION } == true) { // In a process of making a call - RecorderNative.stopRecording?.invoke() - stop() + RecorderInterface.stopRecording?.invoke() + AudioPlayer.stop() } super.onPlaybackConfigChanged(configs) } @@ -154,7 +141,7 @@ object AudioPlayer { } VideoPlayer.stopAll() - RecorderNative.stopRecording?.invoke() + RecorderInterface.stopRecording?.invoke() val current = currentlyPlaying.value if (current == null || current.first != filePath) { stopListener() @@ -208,16 +195,16 @@ object AudioPlayer { return player.currentPosition } - fun stop() { + override fun stop() { if (currentlyPlaying.value == null) return player.stop() stopListener() } - fun stop(item: ChatItem) = stop(item.file?.fileName) + override fun stop(item: ChatItem) = stop(item.file?.fileName) // FileName or filePath are ok - fun stop(fileName: String?) { + override fun stop(fileName: String?) { if (fileName != null && currentlyPlaying.value?.first?.endsWith(fileName) == true) { stop() } @@ -241,7 +228,7 @@ object AudioPlayer { progressJob = null } - fun play( + override fun play( filePath: String?, audioPlaying: MutableState, progress: MutableState, @@ -269,19 +256,19 @@ object AudioPlayer { realDuration?.let { duration.value = it } } - fun pause(audioPlaying: MutableState, pro: MutableState) { + override fun pause(audioPlaying: MutableState, pro: MutableState) { pro.value = pause() audioPlaying.value = false } - fun seekTo(ms: Int, pro: MutableState, filePath: String?) { + override fun seekTo(ms: Int, pro: MutableState, filePath: String?) { pro.value = ms - if (this.currentlyPlaying.value?.first == filePath) { + if (currentlyPlaying.value?.first == filePath) { player.seekTo(ms) } } - fun duration(filePath: String): Int? { + override fun duration(filePath: String): Int? { var res: Int? = null kotlin.runCatching { helperPlayer.setDataSource(filePath) @@ -294,3 +281,5 @@ object AudioPlayer { return res } } + +actual typealias SoundPlayer = chat.simplex.common.helpers.SoundPlayer diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt new file mode 100644 index 0000000000..2604e0d268 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt @@ -0,0 +1,51 @@ +package chat.simplex.common.platform + +import android.annotation.SuppressLint +import android.app.UiModeManager +import android.content.Context +import android.content.SharedPreferences +import android.content.res.Configuration +import android.text.BidiFormatter +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.AppPreferences +import com.russhwolf.settings.Settings +import com.russhwolf.settings.SharedPreferencesSettings +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.desc.desc + +@SuppressLint("DiscouragedApi") +@Composable +actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font { + val context = LocalContext.current + val id = context.resources.getIdentifier(res, "font", context.packageName) + return Font(id, weight, style) +} + +actual fun StringResource.localized(): String = desc().toString(context = androidAppContext) + +actual fun isInNightMode() = + (androidAppContext.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager).nightMode == UiModeManager.MODE_NIGHT_YES + +private val sharedPreferences: SharedPreferences by lazy { androidAppContext.getSharedPreferences(AppPreferences.SHARED_PREFS_ID, Context.MODE_PRIVATE) } +private val sharedPreferencesThemes: SharedPreferences by lazy { androidAppContext.getSharedPreferences(AppPreferences.SHARED_PREFS_THEMES_ID, Context.MODE_PRIVATE) } + +actual val settings: Settings by lazy { SharedPreferencesSettings(sharedPreferences) } +actual val settingsThemes: Settings by lazy { SharedPreferencesSettings(sharedPreferencesThemes) } + +actual fun screenOrientation(): ScreenOrientation = when (mainActivity.get()?.resources?.configuration?.orientation) { + Configuration.ORIENTATION_PORTRAIT -> ScreenOrientation.PORTRAIT + Configuration.ORIENTATION_LANDSCAPE -> ScreenOrientation.LANDSCAPE + else -> ScreenOrientation.UNDEFINED +} + +@Composable +actual fun screenWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp + +actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Share.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt similarity index 58% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Share.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt index 36559d71f4..811974b2d5 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Share.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt @@ -1,35 +1,35 @@ -package chat.simplex.app.platform +package chat.simplex.common.platform import android.Manifest import android.content.* +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.net.Uri import android.provider.MediaStore -import android.util.Log import android.webkit.MimeTypeMap -import android.widget.Toast -import androidx.core.content.ContextCompat -import androidx.core.content.FileProvider -import chat.simplex.app.* -import chat.simplex.app.model.CIFile -import chat.simplex.app.views.helpers.generalGetString -import chat.simplex.res.MR +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.UriHandler +import chat.simplex.common.helpers.toUri +import chat.simplex.common.model.CIFile +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.views.helpers.getAppFileUri import java.io.BufferedOutputStream import java.io.File +import chat.simplex.res.MR -fun shareText(text: String) { +actual fun ClipboardManager.shareText(text: String) { val sendIntent: Intent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, text) type = "text/plain" + flags = FLAG_ACTIVITY_NEW_TASK } val shareIntent = Intent.createChooser(sendIntent, null) - // This flag is needed when you start a new activity from non-Activity context - shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - SimplexApp.context.startActivity(shareIntent) + shareIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) + androidAppContext.startActivity(shareIntent) } -fun shareFile(text: String, filePath: String) { - val uri = FileProvider.getUriForFile(SimplexApp.context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) +actual fun shareFile(text: String, filePath: String) { + val uri = getAppFileUri(filePath.substringAfterLast(File.separator)) val ext = filePath.substringAfterLast(".") val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: return val sendIntent: Intent = Intent().apply { @@ -37,28 +37,22 @@ fun shareFile(text: String, filePath: String) { /*if (text.isNotEmpty()) { putExtra(Intent.EXTRA_TEXT, text) }*/ - putExtra(Intent.EXTRA_STREAM, uri) + putExtra(Intent.EXTRA_STREAM, uri.toUri()) type = mimeType + flags = Intent.FLAG_ACTIVITY_NEW_TASK } val shareIntent = Intent.createChooser(sendIntent, null) - // This flag is needed when you start a new activity from non-Activity context - shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - SimplexApp.context.startActivity(shareIntent) + shareIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) + androidAppContext.startActivity(shareIntent) } -fun copyText(text: String) { - val clipboard = ContextCompat.getSystemService(SimplexApp.context, ClipboardManager::class.java) - clipboard?.setPrimaryClip(ClipData.newPlainText("text", text)) -} - -fun sendEmail(subject: String, body: CharSequence) { +actual fun UriHandler.sendEmail(subject: String, body: CharSequence) { val emailIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject) emailIntent.putExtra(Intent.EXTRA_TEXT, body) - // This flag is needed when you start a new activity from non-Activity context emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) try { - SimplexApp.context.startActivity(emailIntent) + androidAppContext.startActivity(emailIntent) } catch (e: ActivityNotFoundException) { Log.e(TAG, "No activity was found for handling email intent") } @@ -78,7 +72,6 @@ fun imageMimeType(fileName: String): String { /** Before calling, make sure the user allows to write to external storage [Manifest.permission.WRITE_EXTERNAL_STORAGE] */ fun saveImage(ciFile: CIFile?) { - val cxt = SimplexApp.context val filePath = getLoadedFilePath(ciFile) val fileName = ciFile?.fileName if (filePath != null && fileName != null) { @@ -87,16 +80,16 @@ fun saveImage(ciFile: CIFile?) { values.put(MediaStore.Images.Media.MIME_TYPE, imageMimeType(fileName)) values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) values.put(MediaStore.MediaColumns.TITLE, fileName) - val uri = cxt.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) + val uri = androidAppContext.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) uri?.let { - cxt.contentResolver.openOutputStream(uri)?.let { stream -> + androidAppContext.contentResolver.openOutputStream(uri)?.let { stream -> val outputStream = BufferedOutputStream(stream) File(filePath).inputStream().use { it.copyTo(outputStream) } outputStream.close() - Toast.makeText(cxt, generalGetString(MR.strings.image_saved), Toast.LENGTH_SHORT).show() + showToast(generalGetString(MR.strings.image_saved)) } } } else { - Toast.makeText(cxt, generalGetString(MR.strings.file_not_found), Toast.LENGTH_SHORT).show() + showToast(generalGetString(MR.strings.file_not_found)) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/UI.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt similarity index 66% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/platform/UI.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt index 4197cffb04..c458561f96 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/UI.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.platform +package chat.simplex.common.platform import android.app.Activity import android.content.Context @@ -7,17 +7,19 @@ import android.graphics.Rect import android.os.Build import android.view.* import android.view.inputmethod.InputMethodManager +import android.widget.Toast import androidx.compose.runtime.* -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView -import chat.simplex.app.SimplexApp -import chat.simplex.app.views.helpers.KeyboardState +import chat.simplex.common.views.helpers.KeyboardState +import androidx.compose.ui.platform.LocalContext as LocalContext1 + +actual fun showToast(text: String, timeout: Long) = Toast.makeText(androidAppContext, text, Toast.LENGTH_SHORT).show() @Composable -fun LockToCurrentOrientationUntilDispose() { - val context = LocalContext.current +actual fun LockToCurrentOrientationUntilDispose() { + val context = LocalContext1.current DisposableEffect(Unit) { - val activity = context as Activity + val activity = (context as Activity?) ?: return@DisposableEffect onDispose {} val manager = context.getSystemService(Activity.WINDOW_SERVICE) as WindowManager val rotation = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) manager.defaultDisplay.rotation else activity.display?.rotation activity.requestedOrientation = when (rotation) { @@ -32,7 +34,10 @@ fun LockToCurrentOrientationUntilDispose() { } @Composable -fun getKeyboardState(): State { +actual fun LocalMultiplatformView(): Any? = LocalView.current + +@Composable +actual fun getKeyboardState(): State { val keyboardState = remember { mutableStateOf(KeyboardState.Closed) } val view = LocalView.current DisposableEffect(view) { @@ -57,5 +62,10 @@ fun getKeyboardState(): State { return keyboardState } -fun hideKeyboard(view: View) = - (SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0) +actual fun hideKeyboard(view: Any?) { + // LALAL + // LocalSoftwareKeyboardController.current?.hide() + if (view is View) { + (androidAppContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0) + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/VideoPlayer.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/VideoPlayer.android.kt similarity index 73% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/VideoPlayer.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/VideoPlayer.android.kt index fcaa6158df..984f83d455 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/VideoPlayer.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/VideoPlayer.android.kt @@ -1,45 +1,43 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.platform -import android.content.Context -import android.graphics.Bitmap import android.media.session.PlaybackState import android.net.Uri -import android.util.Log import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf -import chat.simplex.app.* -import chat.simplex.app.R +import androidx.compose.ui.graphics.ImageBitmap +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR import com.google.android.exoplayer2.* import com.google.android.exoplayer2.C.* import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.upstream.DefaultDataSource import com.google.android.exoplayer2.upstream.DefaultHttpDataSource -import chat.simplex.res.MR import kotlinx.coroutines.* import java.io.File +import java.net.URI -class VideoPlayer private constructor( - private val uri: Uri, +actual class VideoPlayer private constructor( + private val uri: URI, private val gallery: Boolean, - private val defaultPreview: Bitmap, + private val defaultPreview: ImageBitmap, defaultDuration: Long, - soundEnabled: Boolean, -) { - companion object { - private val players: MutableMap, VideoPlayer> = mutableMapOf() - private val previewsAndDurations: MutableMap = mutableMapOf() + soundEnabled: Boolean +): VideoPlayerInterface { + actual companion object { + private val players: MutableMap, VideoPlayer> = mutableMapOf() + private val previewsAndDurations: MutableMap = mutableMapOf() - fun getOrCreate( - uri: Uri, + actual fun getOrCreate( + uri: URI, gallery: Boolean, - defaultPreview: Bitmap, + defaultPreview: ImageBitmap, defaultDuration: Long, - soundEnabled: Boolean, + soundEnabled: Boolean ): VideoPlayer = players.getOrPut(uri to gallery) { VideoPlayer(uri, gallery, defaultPreview, defaultDuration, soundEnabled) } - fun enableSound(enable: Boolean, fileName: String?, gallery: Boolean): Boolean = + actual fun enableSound(enable: Boolean, fileName: String?, gallery: Boolean): Boolean = player(fileName, gallery)?.enableSound(enable) == true private fun player(fileName: String?, gallery: Boolean): VideoPlayer? { @@ -47,36 +45,34 @@ class VideoPlayer private constructor( return players.values.firstOrNull { player -> player.uri.path?.endsWith(fileName) == true && player.gallery == gallery } } - fun release(uri: Uri, gallery: Boolean, remove: Boolean) = - player(uri.path, gallery)?.release(remove) + actual fun release(uri: URI, gallery: Boolean, remove: Boolean) = + player(uri.path, gallery)?.release(remove).run { } - fun stopAll() { + actual fun stopAll() { players.values.forEach { it.stop() } } - fun releaseAll() { + actual fun releaseAll() { players.values.forEach { it.release(false) } players.clear() previewsAndDurations.clear() } } - data class PreviewAndDuration(val preview: Bitmap?, val duration: Long?, val timestamp: Long) - private val currentVolume: Float - val soundEnabled: MutableState = mutableStateOf(soundEnabled) - val brokenVideo: MutableState = mutableStateOf(false) - val videoPlaying: MutableState = mutableStateOf(false) - val progress: MutableState = mutableStateOf(0L) - val duration: MutableState = mutableStateOf(defaultDuration) - val preview: MutableState = mutableStateOf(defaultPreview) + override val soundEnabled: MutableState = mutableStateOf(soundEnabled) + override val brokenVideo: MutableState = mutableStateOf(false) + override val videoPlaying: MutableState = mutableStateOf(false) + override val progress: MutableState = mutableStateOf(0L) + override val duration: MutableState = mutableStateOf(defaultDuration) + override val preview: MutableState = mutableStateOf(defaultPreview) init { setPreviewAndDuration() } - val player = ExoPlayer.Builder(SimplexApp.context, - DefaultRenderersFactory(SimplexApp.context)) + val player = ExoPlayer.Builder(androidAppContext, + DefaultRenderersFactory(androidAppContext)) /*.setLoadControl(DefaultLoadControl.Builder() .setPrioritizeTimeOverSizeThresholds(false) // Could probably save some megabytes in memory in case it will be needed .createDefaultLoadControl())*/ @@ -84,20 +80,20 @@ class VideoPlayer private constructor( .setSeekForwardIncrementMs(10_000) .build() .apply { - // Repeat the same track endlessly - repeatMode = Player.REPEAT_MODE_ONE - currentVolume = volume - if (!soundEnabled) { - volume = 0f + // Repeat the same track endlessly + repeatMode = Player.REPEAT_MODE_ONE + currentVolume = volume + if (!soundEnabled) { + volume = 0f + } + setAudioAttributes( + AudioAttributes.Builder() + .setContentType(CONTENT_TYPE_MUSIC) + .setUsage(USAGE_MEDIA) + .build(), + true // disallow to play multiple instances simultaneously + ) } - setAudioAttributes( - AudioAttributes.Builder() - .setContentType(CONTENT_TYPE_MUSIC) - .setUsage(USAGE_MEDIA) - .build(), - true // disallow to play multiple instances simultaneously - ) - } private val listener: MutableState<((position: Long?, state: TrackState) -> Unit)?> = mutableStateOf(null) private var progressJob: Job? = null @@ -115,14 +111,14 @@ class VideoPlayer private constructor( } if (soundEnabled.value) { - RecorderNative.stopRecording?.invoke() + RecorderInterface.stopRecording?.invoke() } AudioPlayer.stop() stopAll() if (listener.value == null) { runCatching { - val dataSourceFactory = DefaultDataSource.Factory(SimplexApp.context, DefaultHttpDataSource.Factory()) - val source = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri)) + val dataSourceFactory = DefaultDataSource.Factory(androidAppContext, DefaultHttpDataSource.Factory()) + val source = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(Uri.parse(uri.toString()))) player.setMediaSource(source, seek ?: 0L) }.onFailure { Log.e(TAG, it.stackTraceToString()) @@ -170,14 +166,14 @@ class VideoPlayer private constructor( override fun onIsPlayingChanged(isPlaying: Boolean) { super.onIsPlayingChanged(isPlaying) // Produce non-ideal transition from stopped to playing state while showing preview image in ChatView -// videoPlaying.value = isPlaying + // videoPlaying.value = isPlaying } }) return true } - fun stop() { + override fun stop() { player.stop() stopListener() } @@ -199,7 +195,7 @@ class VideoPlayer private constructor( progressJob = null } - fun play(resetOnEnd: Boolean) { + override fun play(resetOnEnd: Boolean) { if (progress.value == duration.value) { progress.value = 0 } @@ -218,14 +214,14 @@ class VideoPlayer private constructor( } } - fun enableSound(enable: Boolean): Boolean { + override fun enableSound(enable: Boolean): Boolean { if (soundEnabled.value == enable) return false soundEnabled.value = enable player.volume = if (enable) currentVolume else 0f return true } - fun release(remove: Boolean) { + override fun release(remove: Boolean) { player.release() if (remove) { players.remove(uri to gallery) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Type.android.kt similarity index 81% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Type.android.kt index 299156ecd0..056ae7495e 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Type.android.kt @@ -1,10 +1,9 @@ -package chat.simplex.app.ui.theme +package chat.simplex.common.ui.theme import androidx.compose.ui.text.font.* import chat.simplex.res.MR -// https://github.com/rsms/inter -val Inter: FontFamily = FontFamily( +actual val Inter: FontFamily = FontFamily( Font(MR.fonts.Inter.regular.fontResourceId), Font(MR.fonts.Inter.italic.fontResourceId, style = FontStyle.Italic), Font(MR.fonts.Inter.bold.fontResourceId, FontWeight.Bold), diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt similarity index 94% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 2310479ce7..a549d985ae 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.call +package chat.simplex.common.views.call import android.Manifest import android.annotation.SuppressLint @@ -9,10 +9,9 @@ import android.media.* import android.os.Build import android.os.PowerManager import android.os.PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK -import android.util.Log import android.view.ViewGroup import android.webkit.* -import androidx.activity.compose.BackHandler +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -26,20 +25,21 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewClientCompat -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.ProfileImage -import chat.simplex.app.views.helpers.withApi -import com.google.accompanist.permissions.rememberMultiplePermissionsState +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.res.MR +import com.google.accompanist.permissions.rememberMultiplePermissionsState import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString @@ -47,20 +47,21 @@ import kotlinx.serialization.encodeToString @SuppressLint("SourceLockedOrientationActivity") @Composable -fun ActiveCallView(chatModel: ChatModel) { +actual fun ActiveCallView() { + val chatModel = ChatModel BackHandler(onBack = { val call = chatModel.activeCall.value if (call != null) withApi { chatModel.callManager.endCall(call) } }) val audioViaBluetooth = rememberSaveable { mutableStateOf(false) } - val ntfModeService = remember { chatModel.controller.appPrefs.notificationsMode.get() == NotificationsMode.SERVICE.name } + val ntfModeService = remember { chatModel.controller.appPrefs.notificationsMode.get() == NotificationsMode.SERVICE } LaunchedEffect(Unit) { // Start service when call happening since it's not already started. // It's needed to prevent Android from shutting down a microphone after a minute or so when screen is off - if (!ntfModeService) SimplexService.start() + if (!ntfModeService) platform.androidServiceStart() } DisposableEffect(Unit) { - val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager var btDeviceCount = 0 val audioCallback = object: AudioDeviceCallback() { override fun onAudioDevicesAdded(addedDevices: Array) { @@ -87,16 +88,16 @@ fun ActiveCallView(chatModel: ChatModel) { } } am.registerAudioDeviceCallback(audioCallback, null) - val pm = (SimplexApp.context.getSystemService(Context.POWER_SERVICE) as PowerManager) + val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager) val proximityLock = if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { - pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, SimplexApp.context.packageName + ":proximityLock") + pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, androidAppContext.packageName + ":proximityLock") } else { null } proximityLock?.acquire() onDispose { // Stop it when call ended - if (!ntfModeService) SimplexService.safeStopService(SimplexApp.context) + if (!ntfModeService) platform.androidServiceSafeStop() dropAudioManagerOverrides() am.unregisterAudioDeviceCallback(audioCallback) proximityLock?.release() @@ -215,7 +216,7 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, audioViaBluetoot } private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState) { - val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager Log.d(TAG, "setCallSound: set audio mode, speaker enabled: $speaker") am.mode = AudioManager.MODE_IN_COMMUNICATION if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -241,7 +242,7 @@ private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState= Build.VERSION_CODES.S) { @@ -351,7 +352,7 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) { InfoText(call.callState.text) val connInfo = call.connectionInfo -// val connInfoText = if (connInfo == null) "" else " (${connInfo.text}, ${connInfo.protocolText})" + // val connInfoText = if (connInfo == null) "" else " (${connInfo.text}, ${connInfo.protocolText})" val connInfoText = if (connInfo == null) "" else " (${connInfo.text})" InfoText(call.encryptionStatus + connInfoText) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/ComposeView.android.kt similarity index 74% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/ComposeView.android.kt index a8223582ae..719dfeea55 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/ComposeView.android.kt @@ -1,32 +1,32 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import android.Manifest import android.content.ActivityNotFoundException import android.content.pm.PackageManager -import android.graphics.Bitmap import android.net.Uri -import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.* +import androidx.compose.ui.graphics.ImageBitmap import androidx.core.content.ContextCompat -import chat.simplex.app.SimplexApp -import chat.simplex.app.platform.resizeImageToStrSize -import chat.simplex.app.views.helpers.* +import chat.simplex.common.helpers.toURI +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import java.net.URI @Composable -fun AttachmentSelection( +actual fun AttachmentSelection( composeState: MutableState, attachmentOption: MutableState, - processPickedFile: (Uri?, String?) -> Unit, - processPickedMedia: (List, String?) -> Unit + processPickedFile: (URI?, String?) -> Unit, + processPickedMedia: (List, String?) -> Unit ) { val cameraLauncher = rememberCameraLauncher { uri: Uri? -> if (uri != null) { - val bitmap: Bitmap? = getBitmapFromUri(uri) + val bitmap: ImageBitmap? = getBitmapFromUri(uri.toURI()) if (bitmap != null) { val imagePreview = resizeImageToStrSize(bitmap, maxDataSize = 14000) - composeState.value = composeState.value.copy(preview = ComposePreview.MediaPreview(listOf(imagePreview), listOf(UploadContent.SimpleImage(uri)))) + composeState.value = composeState.value.copy(preview = ComposePreview.MediaPreview(listOf(imagePreview), listOf(UploadContent.SimpleImage(uri.toURI())))) } } } @@ -34,20 +34,19 @@ fun AttachmentSelection( if (isGranted) { cameraLauncher.launchWithFallback() } else { - Toast.makeText(SimplexApp.context, generalGetString(MR.strings.toast_permission_denied), Toast.LENGTH_SHORT).show() + showToast(generalGetString(MR.strings.toast_permission_denied)) } } - val galleryImageLauncher = rememberLauncherForActivityResult(contract = PickMultipleImagesFromGallery()) { processPickedMedia(it, null) } - val galleryImageLauncherFallback = rememberGetMultipleContentsLauncher { processPickedMedia(it, null) } - val galleryVideoLauncher = rememberLauncherForActivityResult(contract = PickMultipleVideosFromGallery()) { processPickedMedia(it, null) } - val galleryVideoLauncherFallback = rememberGetMultipleContentsLauncher { processPickedMedia(it, null) } - - val filesLauncher = rememberGetContentLauncher { processPickedFile(it, null) } + val galleryImageLauncher = rememberLauncherForActivityResult(contract = PickMultipleImagesFromGallery()) { processPickedMedia(it.map { it.toURI() }, null) } + val galleryImageLauncherFallback = rememberGetMultipleContentsLauncher { processPickedMedia(it.map { it.toURI() }, null) } + val galleryVideoLauncher = rememberLauncherForActivityResult(contract = PickMultipleVideosFromGallery()) { processPickedMedia(it.map { it.toURI() }, null) } + val galleryVideoLauncherFallback = rememberGetMultipleContentsLauncher { processPickedMedia(it.map { it.toURI() }, null) } + val filesLauncher = rememberGetContentLauncher { processPickedFile(it?.toURI(), null) } LaunchedEffect(attachmentOption.value) { when (attachmentOption.value) { AttachmentOption.CameraPhoto -> { when (PackageManager.PERMISSION_GRANTED) { - ContextCompat.checkSelfPermission(SimplexApp.context, Manifest.permission.CAMERA) -> { + ContextCompat.checkSelfPermission(androidAppContext, Manifest.permission.CAMERA) -> { cameraLauncher.launchWithFallback() } else -> { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.android.kt similarity index 68% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.android.kt index d7ef0895ac..79361dc073 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.android.kt @@ -1,12 +1,13 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import android.Manifest import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import chat.simplex.common.views.chat.ScanCodeLayout import com.google.accompanist.permissions.rememberPermissionState @Composable -fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { +actual fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/SendMsgView.android.kt similarity index 77% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/SendMsgView.android.kt index eac0bc3b85..8fb2263c7b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/SendMsgView.android.kt @@ -1,17 +1,17 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import android.Manifest import androidx.compose.runtime.Composable import com.google.accompanist.permissions.rememberMultiplePermissionsState @Composable -fun allowedToRecordVoiceByPlatform(): Boolean { +actual fun allowedToRecordVoiceByPlatform(): Boolean { val permissionsState = rememberMultiplePermissionsState(listOf(Manifest.permission.RECORD_AUDIO)) return permissionsState.allPermissionsGranted } @Composable -fun VoiceButtonWithoutPermissionByPlatform() { +actual fun VoiceButtonWithoutPermissionByPlatform() { val permissionsState = rememberMultiplePermissionsState(listOf(Manifest.permission.RECORD_AUDIO)) VoiceButtonWithoutPermission { permissionsState.launchMultiplePermissionRequest() } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt similarity index 57% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt index 3c3337db88..25cbffb651 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt @@ -1,37 +1,37 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.net.Uri -import android.os.Build -import androidx.compose.runtime.* -import androidx.compose.ui.graphics.* +import android.os.Build.VERSION.SDK_INT +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.* -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.getLoadedFilePath -import chat.simplex.app.platform.hideKeyboard -import chat.simplex.app.views.helpers.* +import androidx.compose.ui.platform.LocalContext +import chat.simplex.common.helpers.toUri +import chat.simplex.common.model.CIFile +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.ModalManager import coil.ImageLoader import coil.compose.rememberAsyncImagePainter import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest +import java.net.URI @Composable -fun SimpleAndAnimatedImageView( - uri: Uri, +actual fun SimpleAndAnimatedImageView( + uri: URI, imageBitmap: ImageBitmap, file: CIFile?, imageProvider: () -> ImageGalleryProvider, ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit ) { + val context = LocalContext.current val imagePainter = rememberAsyncImagePainter( - ImageRequest.Builder(SimplexApp.context).data(data = uri).size(coil.size.Size.ORIGINAL).build(), + ImageRequest.Builder(context).data(data = uri.toUri()).size(coil.size.Size.ORIGINAL).build(), placeholder = BitmapPainter(imageBitmap), // show original image while it's still loading by coil imageLoader = imageLoader ) - val view = LocalView.current + val view = LocalMultiplatformView() ImageView(imagePainter) { hideKeyboard(view) if (getLoadedFilePath(file) != null) { @@ -42,9 +42,9 @@ fun SimpleAndAnimatedImageView( } } -private val imageLoader = ImageLoader.Builder(SimplexApp.context) +private val imageLoader = ImageLoader.Builder(androidAppContext) .components { - if (Build.VERSION.SDK_INT >= 28) { + if (SDK_INT >= 28) { add(ImageDecoderDecoder.Factory()) } else { add(GifDecoder.Factory()) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt similarity index 56% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt index 585c1eb229..f2f3e27766 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVideoView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt @@ -1,20 +1,22 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import android.graphics.Rect -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.runtime.* -import androidx.compose.ui.* -import androidx.compose.ui.platform.* -import androidx.compose.ui.unit.* +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import chat.simplex.app.views.helpers.* +import chat.simplex.common.platform.VideoPlayer import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH import com.google.android.exoplayer2.ui.StyledPlayerView - @Composable -fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: () -> Unit, stop: () -> Unit) { +actual fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: () -> Unit, stop: () -> Unit) { AndroidView( factory = { ctx -> StyledPlayerView(ctx).apply { @@ -32,7 +34,8 @@ fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: ) } -@Composable fun LocalWindowWidth(): Dp { +@Composable +actual fun LocalWindowWidth(): Dp { val view = LocalView.current val density = LocalDensity.current.density return remember { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt similarity index 67% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt index ef0eb89c86..c056728640 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt @@ -1,20 +1,21 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import android.Manifest import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import chat.simplex.app.model.ChatItem -import chat.simplex.app.model.MsgContent -import chat.simplex.app.platform.saveImage +import chat.simplex.common.model.ChatItem +import chat.simplex.common.model.MsgContent +import chat.simplex.common.platform.FileChooserLauncher +import chat.simplex.common.platform.saveImage +import chat.simplex.common.views.helpers.withApi import chat.simplex.res.MR import com.google.accompanist.permissions.rememberPermissionState import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable -fun SaveContentItemAction(cItem: ChatItem, showMenu: MutableState) { - val saveFileLauncher = rememberSaveFileLauncher(ciFile = cItem.file) +actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserLauncher, showMenu: MutableState) { val writePermissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE) ItemAction(stringResource(MR.strings.save_verb), painterResource(MR.images.ic_download), onClick = { when (cItem.content.msgContent) { @@ -25,7 +26,7 @@ fun SaveContentItemAction(cItem: ChatItem, showMenu: MutableState) { writePermissionState.launchPermissionRequest() } } - is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> saveFileLauncher.launch(cItem.file?.fileName) + is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") } else -> {} } showMenu.value = false diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.android.kt similarity index 75% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.android.kt index 861f2b8f66..d23ee58db2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.android.kt @@ -1,7 +1,5 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.graphics.Bitmap -import android.net.Uri import android.os.Build import android.view.View import androidx.compose.foundation.Image @@ -13,7 +11,8 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible -import chat.simplex.app.views.helpers.VideoPlayer +import chat.simplex.common.helpers.toUri +import chat.simplex.common.platform.VideoPlayer import chat.simplex.res.MR import coil.ImageLoader import coil.compose.rememberAsyncImagePainter @@ -21,13 +20,13 @@ import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest import coil.size.Size -import com.google.android.exoplayer2.R import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import com.google.android.exoplayer2.ui.StyledPlayerView import dev.icerock.moko.resources.compose.stringResource +import java.net.URI @Composable -fun FullScreenImageView(modifier: Modifier, uri: Uri, imageBitmap: Bitmap) { +actual fun FullScreenImageView(modifier: Modifier, uri: URI, imageBitmap: ImageBitmap) { // I'm making a new instance of imageLoader here because if I use one instance in multiple places // after end of composition here a GIF from the first instance will be paused automatically which isn't what I want val imageLoader = ImageLoader.Builder(LocalContext.current) @@ -41,8 +40,8 @@ fun FullScreenImageView(modifier: Modifier, uri: Uri, imageBitmap: Bitmap) { .build() Image( rememberAsyncImagePainter( - ImageRequest.Builder(LocalContext.current).data(data = uri).size(Size.ORIGINAL).build(), - placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil + ImageRequest.Builder(LocalContext.current).data(data = uri.toUri()).size(Size.ORIGINAL).build(), + placeholder = BitmapPainter(imageBitmap), // show original image while it's still loading by coil imageLoader = imageLoader ), contentDescription = stringResource(MR.strings.image_descr), @@ -52,7 +51,7 @@ fun FullScreenImageView(modifier: Modifier, uri: Uri, imageBitmap: Bitmap) { } @Composable -fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier) { +actual fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier) { AndroidView( factory = { ctx -> StyledPlayerView(ctx).apply { @@ -66,8 +65,8 @@ fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier) { setShowSubtitleButton(false) setShowVrButton(false) controllerAutoShow = false - findViewById(R.id.exo_controls_background).setBackgroundColor(Color.Black.copy(alpha = 0.3f).toArgb()) - findViewById(R.id.exo_settings).isVisible = false + findViewById(com.google.android.exoplayer2.R.id.exo_controls_background).setBackgroundColor(Color.Black.copy(alpha = 0.3f).toArgb()) + findViewById(com.google.android.exoplayer2.R.id.exo_settings).isVisible = false this.player = player.player } }, diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.android.kt new file mode 100644 index 0000000000..0b99e07582 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.android.kt @@ -0,0 +1,16 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.Dialog + +@Composable +actual fun DefaultDialog( + onDismissRequest: () -> Unit, + content: @Composable () -> Unit +) { + Dialog( + onDismissRequest = onDismissRequest + ) { + content() + } +} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt new file mode 100644 index 0000000000..dc56ead544 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt @@ -0,0 +1,46 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.PopupProperties + +actual interface DefaultExposedDropdownMenuBoxScope { + @Composable + actual fun DefaultExposedDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier, + content: @Composable ColumnScope.() -> Unit + ) { + DropdownMenu(expanded, onDismissRequest, modifier, content = content) + } + + @Composable + fun DropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + offset: DpOffset = DpOffset(0.dp, 0.dp), + properties: PopupProperties = PopupProperties(focusable = true), + content: @Composable ColumnScope.() -> Unit + ) { + androidx.compose.material.DropdownMenu(expanded, onDismissRequest, modifier, offset, properties, content) + } +} + +@Composable +actual fun DefaultExposedDropdownMenuBox( + expanded: Boolean, + onExpandedChange: (Boolean) -> Unit, + modifier: Modifier, + content: @Composable DefaultExposedDropdownMenuBoxScope.() -> Unit +) { + val scope = remember { object : DefaultExposedDropdownMenuBoxScope {} } + androidx.compose.material.ExposedDropdownMenuBox(expanded, onExpandedChange, modifier, content = { + scope.content() + }) +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt index 301ae120e7..95dac842f2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import android.Manifest import android.app.Activity @@ -9,8 +9,6 @@ import android.graphics.* import android.net.Uri import android.provider.MediaStore import android.util.Base64 -import android.util.Log -import android.widget.Toast import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContract @@ -22,19 +20,22 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.content.FileProvider -import chat.simplex.app.* -import chat.simplex.app.model.json -import chat.simplex.app.platform.getAppFilesDirectory -import chat.simplex.app.views.newchat.ActionButton +import chat.simplex.common.helpers.APPLICATION_ID +import chat.simplex.common.helpers.toURI +import chat.simplex.common.model.json +import chat.simplex.common.platform.* +import chat.simplex.common.views.newchat.ActionButton import chat.simplex.res.MR import kotlinx.serialization.builtins.* import java.io.File +import java.net.URI val errorBitmapBytes = Base64.decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg==", Base64.NO_WRAP) val errorBitmap: Bitmap = BitmapFactory.decodeByteArray(errorBitmapBytes, 0, errorBitmapBytes.size) @@ -45,7 +46,7 @@ class CustomTakePicturePreview(var uri: Uri?, var tmpFile: File?): ActivityResul tmpFile = File.createTempFile("image", ".bmp", File(getAppFilesDirectory())) // Since the class should return Uri, the file should be deleted somewhere else. And in order to be sure, delegate this to system tmpFile?.deleteOnExit() - uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", tmpFile!!) + uri = FileProvider.getUriForFile(context, "$APPLICATION_ID.provider", tmpFile!!) return Intent(MediaStore.ACTION_IMAGE_CAPTURE) .putExtra(MediaStore.EXTRA_OUTPUT, uri) } @@ -127,7 +128,7 @@ fun ManagedActivityResultLauncher.launchWithFallback() { try { // Try to open any camera just to capture an image, will not be returned like with previous intent - SimplexApp.context.startActivity(Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA).also { it.addFlags(FLAG_ACTIVITY_NEW_TASK) }) + androidAppContext.startActivity(Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA).also { it.addFlags(FLAG_ACTIVITY_NEW_TASK) }) } catch (e: ActivityNotFoundException) { // No camera apps available at all Log.e(TAG, "Camera launcher2: " + e.stackTraceToString()) @@ -136,14 +137,15 @@ fun ManagedActivityResultLauncher.launchWithFallback() { } @Composable -fun GetImageBottomSheet( - imageBitmap: MutableState, - onImageChange: (Bitmap) -> Unit, +actual fun GetImageBottomSheet( + imageBitmap: MutableState, + onImageChange: (ImageBitmap) -> Unit, hideBottomSheet: () -> Unit ) { val context = LocalContext.current val processPickedImage = { uri: Uri? -> if (uri != null) { + val uri = uri.toURI() val bitmap = getBitmapFromUri(uri) if (bitmap != null) { imageBitmap.value = uri @@ -159,7 +161,7 @@ fun GetImageBottomSheet( cameraLauncher.launchWithFallback() hideBottomSheet() } else { - Toast.makeText(context, generalGetString(MR.strings.toast_permission_denied), Toast.LENGTH_SHORT).show() + showToast(generalGetString(MR.strings.toast_permission_denied)) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt index 6eed47b800..b238bdf7ca 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import android.os.Build.VERSION.SDK_INT import androidx.biometric.BiometricManager @@ -6,15 +6,14 @@ import androidx.biometric.BiometricManager.Authenticators.* import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity -import chat.simplex.app.SimplexApp -import chat.simplex.app.platform.mainActivity -import chat.simplex.app.views.usersettings.LAMode +import chat.simplex.common.platform.mainActivity +import chat.simplex.common.views.usersettings.LAMode -fun authenticate( +actual fun authenticate( promptTitle: String, promptSubtitle: String, - selfDestruct: Boolean = false, - usingLAMode: LAMode = SimplexApp.context.chatModel.controller.appPrefs.laMode.get(), + selfDestruct: Boolean, + usingLAMode: LAMode, completed: (LAResult) -> Unit ) { val activity = mainActivity.get() ?: return completed(LAResult.Error("")) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt similarity index 77% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Util.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt index a36bf42de6..23bd5491f2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt @@ -1,20 +1,20 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import android.app.Application -//import android.app.LocaleManager import android.content.res.Resources import android.graphics.* import android.graphics.Typeface import android.graphics.drawable.Drawable import android.media.MediaMetadataRetriever -import android.net.Uri import android.os.* import android.provider.OpenableColumns import android.text.Spanned import android.text.SpannedString import android.text.style.* -import android.util.Log -import androidx.compose.runtime.* +import android.util.Base64 +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.* import androidx.compose.ui.text.* @@ -24,12 +24,13 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.* import androidx.core.content.FileProvider import androidx.core.text.HtmlCompat -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.getLoadedFilePath +import chat.simplex.common.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import java.io.* +import java.net.URI fun Spanned.toHtmlWithoutParagraphs(): String { return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) @@ -46,19 +47,10 @@ fun Resources.getText(id: StringResource, vararg args: Any): CharSequence { return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY) } -fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString { +actual fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString { return spannableStringToAnnotatedString(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY), density) } -@Composable -fun annotatedStringResource(id: StringResource): AnnotatedString { - val density = LocalDensity.current - return remember(id) { - val text = id.getString(SimplexApp.context) - escapedHtmlToAnnotatedString(text, density) - } -} - private fun spannableStringToAnnotatedString( text: CharSequence, density: Density, @@ -163,17 +155,20 @@ private fun spannableStringToAnnotatedString( } } +actual fun getAppFileUri(fileName: String): URI = + FileProvider.getUriForFile(androidAppContext, "$APPLICATION_ID.provider", File(getAppFilePath(fileName))).toURI() + // https://developer.android.com/training/data-storage/shared/documents-files#bitmap -fun getLoadedImage(file: CIFile?): Bitmap? { +actual fun getLoadedImage(file: CIFile?): ImageBitmap? { val filePath = getLoadedFilePath(file) return if (filePath != null) { try { - val uri = FileProvider.getUriForFile(SimplexApp.context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) - val parcelFileDescriptor = SimplexApp.context.contentResolver.openFileDescriptor(uri, "r") + val uri = getAppFileUri(filePath.substringAfterLast(File.separator)) + val parcelFileDescriptor = androidAppContext.contentResolver.openFileDescriptor(uri.toUri(), "r") val fileDescriptor = parcelFileDescriptor?.fileDescriptor val image = decodeSampledBitmapFromFileDescriptor(fileDescriptor, 1000, 1000) parcelFileDescriptor?.close() - image + image.asImageBitmap() } catch (e: Exception) { null } @@ -215,33 +210,33 @@ private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, return inSampleSize } -fun getFileName(uri: Uri): String? { - return SimplexApp.context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> +actual fun getFileName(uri: URI): String? { + return androidAppContext.contentResolver.query(uri.toUri(), null, null, null, null)?.use { cursor -> val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.moveToFirst() cursor.getString(nameIndex) } } -fun getAppFilePath(uri: Uri): String? { - return SimplexApp.context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> +actual fun getAppFilePath(uri: URI): String? { + return androidAppContext.contentResolver.query(uri.toUri(), null, null, null, null)?.use { cursor -> val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.moveToFirst() - chat.simplex.app.platform.getAppFilePath(cursor.getString(nameIndex)) + getAppFilePath(cursor.getString(nameIndex)) } } -fun getFileSize(uri: Uri): Long? { - return SimplexApp.context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> +actual fun getFileSize(uri: URI): Long? { + return androidAppContext.contentResolver.query(uri.toUri(), null, null, null, null)?.use { cursor -> val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) cursor.moveToFirst() cursor.getLong(sizeIndex) } } -fun getBitmapFromUri(uri: Uri, withAlertOnException: Boolean = true): Bitmap? { +actual fun getBitmapFromUri(uri: URI, withAlertOnException: Boolean): ImageBitmap? { return if (Build.VERSION.SDK_INT >= 28) { - val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri) + val source = ImageDecoder.createSource(androidAppContext.contentResolver, uri.toUri()) try { ImageDecoder.decodeBitmap(source) } catch (e: android.graphics.ImageDecoder.DecodeException) { @@ -256,12 +251,12 @@ fun getBitmapFromUri(uri: Uri, withAlertOnException: Boolean = true): Bitmap? { } } else { BitmapFactory.decodeFile(getAppFilePath(uri)) - } + }?.asImageBitmap() } -fun getDrawableFromUri(uri: Uri, withAlertOnException: Boolean = true): Drawable? { +actual fun getDrawableFromUri(uri: URI, withAlertOnException: Boolean): Any? { return if (Build.VERSION.SDK_INT >= 28) { - val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri) + val source = ImageDecoder.createSource(androidAppContext.contentResolver, uri.toUri()) try { ImageDecoder.decodeDrawable(source) } catch (e: android.graphics.ImageDecoder.DecodeException) { @@ -279,17 +274,16 @@ fun getDrawableFromUri(uri: Uri, withAlertOnException: Boolean = true): Drawable } } -fun saveTempImageUncompressed(image: Bitmap, asPng: Boolean): File? { +actual suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean): File? { return try { val ext = if (asPng) "png" else "jpg" - val tmpDir = SimplexApp.context.getDir("temp", Application.MODE_PRIVATE) - return File(tmpDir.absolutePath + File.separator + generateNewFileName("IMG", ext)).apply { + return File(getTempFilesDirectory() + File.separator + generateNewFileName("IMG", ext)).apply { outputStream().use { out -> - image.compress(if (asPng) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG, 85, out) + image.asAndroidBitmap().compress(if (asPng) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG, 85, out) out.flush() } deleteOnExit() - SimplexApp.context.chatModel.filesToDelete.add(this) + ChatModel.filesToDelete.add(this) } } catch (e: Exception) { Log.e(TAG, "Util.kt saveTempImageUncompressed error: ${e.message}") @@ -297,9 +291,9 @@ fun saveTempImageUncompressed(image: Bitmap, asPng: Boolean): File? { } } -fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true): VideoPlayer.PreviewAndDuration { +actual fun getBitmapFromVideo(uri: URI, timestamp: Long?, random: Boolean): VideoPlayerInterface.PreviewAndDuration { val mmr = MediaMetadataRetriever() - mmr.setDataSource(SimplexApp.context, uri) + mmr.setDataSource(androidAppContext, uri.toUri()) val durationMs = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() val image = when { timestamp != null -> mmr.getFrameAtTime(timestamp * 1000, MediaMetadataRetriever.OPTION_CLOSEST) @@ -307,5 +301,9 @@ fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true else -> mmr.getFrameAtTime(0) } mmr.release() - return VideoPlayer.PreviewAndDuration(image, durationMs, timestamp ?: 0) + return VideoPlayerInterface.PreviewAndDuration(image?.asImageBitmap(), durationMs, timestamp ?: 0) } + +actual fun ByteArray.toBase64StringForPassphrase(): String = Base64.encodeToString(this, Base64.DEFAULT) + +actual fun String.toByteArrayFromBase64ForPassphrase(): ByteArray = Base64.decode(this, Base64.DEFAULT) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCode.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCode.android.kt new file mode 100644 index 0000000000..eee525827e --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCode.android.kt @@ -0,0 +1,18 @@ +package chat.simplex.common.views.newchat + +import androidx.compose.ui.graphics.* + +actual fun ImageBitmap.replaceColor(from: Int, to: Int): ImageBitmap { + val pixels = IntArray(width * height) + val bitmap = this.asAndroidBitmap() + bitmap.getPixels(pixels, 0, width, 0, 0, width, height) + var i = 0 + while (i < pixels.size) { + if (pixels[i] == from) { + pixels[i] = to + } + i++ + } + bitmap.setPixels(pixels, 0, width, 0, 0, width, height) + return bitmap.asImageBitmap() +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt index e0ee944195..7fb6445d57 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import android.annotation.SuppressLint import android.util.Log @@ -7,6 +7,8 @@ import androidx.camera.core.* import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView @@ -16,14 +18,14 @@ import boofcv.alg.color.ColorFormat import boofcv.android.ConvertCameraImage import boofcv.factory.fiducial.FactoryFiducial import boofcv.struct.image.GrayU8 -import chat.simplex.app.TAG +import chat.simplex.common.platform.TAG import com.google.common.util.concurrent.ListenableFuture import java.util.concurrent.* // Adapted from learntodroid - https://gist.github.com/learntodroid/8f839be0b29d0378f843af70607bd7f5 @Composable -fun QRCodeScanner(onBarcode: (String) -> Unit) { +actual fun QRCodeScanner(onBarcode: (String) -> Unit) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current var preview by remember { mutableStateOf(null) } @@ -50,7 +52,8 @@ fun QRCodeScanner(onBarcode: (String) -> Unit) { ) implementationMode = PreviewView.ImplementationMode.COMPATIBLE } - } + }, + modifier = Modifier.clipToBounds() ) { previewView -> val cameraSelector: CameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt similarity index 74% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt index dd159a4c91..ee825730ca 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt @@ -1,13 +1,13 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import android.Manifest import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import chat.simplex.app.model.ChatModel +import chat.simplex.common.model.ChatModel import com.google.accompanist.permissions.rememberPermissionState @Composable -fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { +actual fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.android.kt similarity index 67% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.android.kt index 0d8ac2ef1c..0944ff4aab 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.android.kt @@ -1,26 +1,26 @@ -package chat.simplex.app.views.onboarding +package chat.simplex.common.views.onboarding import android.Manifest import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import chat.simplex.app.SimplexApp +import chat.simplex.common.platform.ntfManager import com.google.accompanist.permissions.rememberPermissionState @Composable -fun SetNotificationsModeAdditions() { +actual fun SetNotificationsModeAdditions() { if (Build.VERSION.SDK_INT >= 33) { val notificationsPermissionState = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS) LaunchedEffect(notificationsPermissionState.hasPermission) { if (notificationsPermissionState.hasPermission) { - SimplexApp.context.chatModel.controller.ntfManager.createNtfChannelsMaybeShowAlert() + ntfManager.createNtfChannelsMaybeShowAlert() } else { notificationsPermissionState.launchPermissionRequest() } } } else { LaunchedEffect(Unit) { - SimplexApp.context.chatModel.controller.ntfManager.createNtfChannelsMaybeShowAlert() + ntfManager.createNtfChannelsMaybeShowAlert() } } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt similarity index 61% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index fba7ecc8ad..e03e19858a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -1,78 +1,60 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced -import SectionItemView -import SectionItemViewSpaceBetween -import SectionSpacer import SectionView import android.app.Activity import android.content.ComponentName -import android.content.Context import android.content.pm.PackageManager import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED -import android.net.Uri -import android.util.Log -import android.widget.Toast -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.helpers.saveAppLocale -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.AppearanceScope.ColorEditor -import chat.simplex.app.views.usersettings.AppearanceScope.LangSelector -import chat.simplex.app.views.usersettings.AppearanceScope.ThemesSection -import com.godaddy.android.colorpicker.* +import chat.simplex.common.R +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* +import chat.simplex.common.helpers.APPLICATION_ID +import chat.simplex.common.helpers.saveAppLocale +import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor import chat.simplex.res.MR import kotlinx.coroutines.delay -import kotlinx.serialization.encodeToString -import java.io.BufferedOutputStream -import java.util.* -import kotlin.collections.ArrayList enum class AppIcon(val resId: Int) { - DEFAULT(R.mipmap.icon), - DARK_BLUE(R.mipmap.icon_dark_blue), + DEFAULT(R.drawable.icon_round_common), + DARK_BLUE(R.drawable.icon_dark_blue_round_common), } @Composable -fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { +actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { val appIcon = remember { mutableStateOf(findEnabledIcon()) } fun setAppIcon(newIcon: AppIcon) { if (appIcon.value == newIcon) return - val newComponent = ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}") - val oldComponent = ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${appIcon.value.name.lowercase()}") - SimplexApp.context.packageManager.setComponentEnabledSetting( + val newComponent = ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}") + val oldComponent = ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${appIcon.value.name.lowercase()}") + androidAppContext.packageManager.setComponentEnabledSetting( newComponent, COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP ) - SimplexApp.context.packageManager.setComponentEnabledSetting( + androidAppContext.packageManager.setComponentEnabledSetting( oldComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP ) @@ -80,7 +62,7 @@ fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> appIcon.value = newIcon } - AppearanceLayout( + AppearanceScope.AppearanceLayout( appIcon, m.controller.appPrefs.appLanguage, m.controller.appPrefs.systemDarkTheme, @@ -94,7 +76,8 @@ fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> ) } -@Composable fun AppearanceLayout( +@Composable +fun AppearanceScope.AppearanceLayout( icon: MutableState, languagePref: SharedPreference, systemDarkTheme: SharedPreference, @@ -108,14 +91,14 @@ fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> AppBarTitle(stringResource(MR.strings.appearance_settings)) SectionView(stringResource(MR.strings.settings_section_title_language), padding = PaddingValues()) { val context = LocalContext.current -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { -// SectionItemWithValue( -// generalGetString(MR.strings.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() }, -// remember { mutableStateOf("system") }, -// listOf(ValueTitleDesc("system", generalGetString(MR.strings.change_verb), "")), -// onSelected = { openSystemLangPicker(context as? Activity ?: return@SectionItemWithValue) } -// ) -// } else { + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // SectionItemWithValue( + // generalGetString(MR.strings.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() }, + // remember { mutableStateOf("system") }, + // listOf(ValueTitleDesc("system", generalGetString(MR.strings.change_verb), "")), + // onSelected = { openSystemLangPicker(context as? Activity ?: return@SectionItemWithValue) } + // ) + // } else { val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } LangSelector(state) { state.value = it @@ -124,14 +107,14 @@ fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> val activity = context as? Activity if (activity != null) { if (it == "system") { - saveAppLocale(languagePref, activity) + activity.saveAppLocale(languagePref) } else { - saveAppLocale(languagePref, activity, it) + activity.saveAppLocale(languagePref, it) } } } } -// } + // } } SectionDividerSpaced() @@ -165,16 +148,16 @@ fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> } private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon -> - SimplexApp.context.packageManager.getComponentEnabledSetting( - ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}") + androidAppContext.packageManager.getComponentEnabledSetting( + ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}") ).let { it == COMPONENT_ENABLED_STATE_DEFAULT || it == COMPONENT_ENABLED_STATE_ENABLED } } -@Preview(showBackground = true) +@Preview @Composable fun PreviewAppearanceSettings() { SimpleXTheme { - AppearanceLayout( + AppearanceScope.AppearanceLayout( icon = remember { mutableStateOf(AppIcon.DARK_BLUE) }, languagePref = SharedPreference({ null }, {}), systemDarkTheme = SharedPreference({ null }, {}), diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.android.kt similarity index 74% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.android.kt index 6b2cc49b0d..3c1771c8b3 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.android.kt @@ -1,25 +1,24 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionView import android.view.WindowManager import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.fragment.app.FragmentActivity -import chat.simplex.app.model.ChatController -import chat.simplex.app.model.ChatModel +import chat.simplex.common.model.ChatModel import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable -fun PrivacyDeviceSection( +actual fun PrivacyDeviceSection( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), setPerformLA: (Boolean) -> Unit, ) { SectionView(stringResource(MR.strings.settings_section_title_device)) { - ChatLockItem(ChatModel, showSettingsModal, setPerformLA) + ChatLockItem(showSettingsModal, setPerformLA) val context = LocalContext.current - SettingsPreferenceItem(painterResource(MR.images.ic_visibility_off), stringResource(MR.strings.protect_app_screen), ChatController.appPrefs.privacyProtectScreen) { on -> + SettingsPreferenceItem(painterResource(MR.images.ic_visibility_off), stringResource(MR.strings.protect_app_screen), ChatModel.controller.appPrefs.privacyProtectScreen) { on -> if (on) { (context as? FragmentActivity)?.window?.setFlags( WindowManager.LayoutParams.FLAG_SECURE, diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt similarity index 72% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt index 443bce6dca..a1b7b3141d 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt @@ -1,13 +1,13 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import android.Manifest import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import chat.simplex.app.model.ServerCfg +import chat.simplex.common.model.ServerCfg import com.google.accompanist.permissions.rememberPermissionState @Composable -fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) { +actual fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt similarity index 77% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt index 4b39102f97..49a29cf141 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt @@ -1,20 +1,19 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionView import androidx.compose.runtime.Composable import androidx.work.WorkManager -import chat.simplex.app.SimplexApp -import chat.simplex.app.SimplexService -import chat.simplex.app.model.ChatModel -import chat.simplex.app.views.helpers.AlertManager -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.AlertManager +import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR import com.jakewharton.processphoenix.ProcessPhoenix import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable -fun SettingsSectionApp( +actual fun SettingsSectionApp( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, @@ -28,14 +27,15 @@ fun SettingsSectionApp( } } + private fun restartApp() { - ProcessPhoenix.triggerRebirth(SimplexApp.context) + ProcessPhoenix.triggerRebirth(androidAppContext) shutdownApp() } private fun shutdownApp() { - WorkManager.getInstance(SimplexApp.context).cancelAllWork() - SimplexService.safeStopService(SimplexApp.context) + WorkManager.getInstance(androidAppContext).cancelAllWork() + platform.androidServiceSafeStop() Runtime.getRuntime().exit(0) } diff --git a/apps/multiplatform/android/src/main/res/drawable/edit_text_cursor.xml b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml similarity index 100% rename from apps/multiplatform/android/src/main/res/drawable/edit_text_cursor.xml rename to apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml diff --git a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c index 8b2977a3e8..fe8dcbd536 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c @@ -19,7 +19,7 @@ extern void __rel_iplt_start(void){}; extern void reallocarray(void){}; JNIEXPORT jint JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_pipeStdOutToSocket(JNIEnv *env, __unused jclass clazz, jstring socket_name) { +Java_chat_simplex_common_platform_CoreKt_pipeStdOutToSocket(JNIEnv *env, __unused jclass clazz, jstring socket_name) { const char *name = (*env)->GetStringUTFChars(env, socket_name, JNI_FALSE); int ret = pipe_std_to_socket(name); (*env)->ReleaseStringUTFChars(env, socket_name, name); @@ -27,7 +27,7 @@ Java_chat_simplex_app_platform_Backend_1commonKt_pipeStdOutToSocket(JNIEnv *env, } JNIEXPORT void JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_initHS(__unused JNIEnv *env, __unused jclass clazz) { +Java_chat_simplex_common_platform_CoreKt_initHS(__unused JNIEnv *env, __unused jclass clazz) { hs_init(NULL, NULL); setLineBuffering(); } @@ -44,7 +44,7 @@ extern char *chat_parse_server(const char *str); extern char *chat_password_hash(const char *pwd, const char *salt); JNIEXPORT jobjectArray JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_chatMigrateInit(JNIEnv *env, __unused jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) { +Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, __unused jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) { const char *_dbPath = (*env)->GetStringUTFChars(env, dbPath, JNI_FALSE); const char *_dbKey = (*env)->GetStringUTFChars(env, dbKey, JNI_FALSE); const char *_confirm = (*env)->GetStringUTFChars(env, confirm, JNI_FALSE); @@ -67,7 +67,7 @@ Java_chat_simplex_app_platform_Backend_1commonKt_chatMigrateInit(JNIEnv *env, __ } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { +Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg)); (*env)->ReleaseStringUTFChars(env, msg, _msg); @@ -75,17 +75,17 @@ Java_chat_simplex_app_platform_Backend_1commonKt_chatSendCmd(JNIEnv *env, __unus } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_chatRecvMsg(JNIEnv *env, __unused jclass clazz, jlong controller) { +Java_chat_simplex_common_platform_CoreKt_chatRecvMsg(JNIEnv *env, __unused jclass clazz, jlong controller) { return (*env)->NewStringUTF(env, chat_recv_msg((void*)controller)); } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_chatRecvMsgWait(JNIEnv *env, __unused jclass clazz, jlong controller, jint wait) { +Java_chat_simplex_common_platform_CoreKt_chatRecvMsgWait(JNIEnv *env, __unused jclass clazz, jlong controller, jint wait) { return (*env)->NewStringUTF(env, chat_recv_msg_wait((void*)controller, wait)); } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_chatParseMarkdown(JNIEnv *env, __unused jclass clazz, jstring str) { +Java_chat_simplex_common_platform_CoreKt_chatParseMarkdown(JNIEnv *env, __unused jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_parse_markdown(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); @@ -93,7 +93,7 @@ Java_chat_simplex_app_platform_Backend_1commonKt_chatParseMarkdown(JNIEnv *env, } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_chatParseServer(JNIEnv *env, __unused jclass clazz, jstring str) { +Java_chat_simplex_common_platform_CoreKt_chatParseServer(JNIEnv *env, __unused jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_parse_server(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); @@ -101,7 +101,7 @@ Java_chat_simplex_app_platform_Backend_1commonKt_chatParseServer(JNIEnv *env, __ } JNIEXPORT jstring JNICALL -Java_chat_simplex_app_platform_Backend_1commonKt_chatPasswordHash(JNIEnv *env, __unused jclass clazz, jstring pwd, jstring salt) { +Java_chat_simplex_common_platform_CoreKt_chatPasswordHash(JNIEnv *env, __unused jclass clazz, jstring pwd, jstring salt) { const char *_pwd = (*env)->GetStringUTFChars(env, pwd, JNI_FALSE); const char *_salt = (*env)->GetStringUTFChars(env, salt, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_password_hash(_pwd, _salt)); diff --git a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c index e772adffa2..2f7299393c 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c @@ -15,7 +15,7 @@ void hs_init(int * argc, char **argv[]); //extern void reallocarray(void){}; JNIEXPORT void JNICALL -Java_chat_simplex_common_platform_BackendKt_initHS(JNIEnv *env, jclass clazz) { +Java_chat_simplex_common_platform_CoreKt_initHS(JNIEnv *env, jclass clazz) { hs_init(NULL, NULL); } @@ -31,7 +31,7 @@ extern char *chat_parse_server(const char *str); extern char *chat_password_hash(const char *pwd, const char *salt); JNIEXPORT jobjectArray JNICALL -Java_chat_simplex_common_platform_BackendKt_chatMigrateInit(JNIEnv *env, jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) { +Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) { const char *_dbPath = (*env)->GetStringUTFChars(env, dbPath, JNI_FALSE); const char *_dbKey = (*env)->GetStringUTFChars(env, dbKey, JNI_FALSE); const char *_confirm = (*env)->GetStringUTFChars(env, confirm, JNI_FALSE); @@ -54,7 +54,7 @@ Java_chat_simplex_common_platform_BackendKt_chatMigrateInit(JNIEnv *env, jclass } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_BackendKt_chatSendCmd(JNIEnv *env, jclass clazz, jlong controller, jstring msg) { +Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, jclass clazz, jlong controller, jstring msg) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg)); (*env)->ReleaseStringUTFChars(env, msg, _msg); @@ -62,17 +62,17 @@ Java_chat_simplex_common_platform_BackendKt_chatSendCmd(JNIEnv *env, jclass claz } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_BackendKt_chatRecvMsg(JNIEnv *env, jclass clazz, jlong controller) { +Java_chat_simplex_common_platform_CoreKt_chatRecvMsg(JNIEnv *env, jclass clazz, jlong controller) { return (*env)->NewStringUTF(env, chat_recv_msg((void*)controller)); } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_BackendKt_chatRecvMsgWait(JNIEnv *env, jclass clazz, jlong controller, jint wait) { +Java_chat_simplex_common_platform_CoreKt_chatRecvMsgWait(JNIEnv *env, jclass clazz, jlong controller, jint wait) { return (*env)->NewStringUTF(env, chat_recv_msg_wait((void*)controller, wait)); } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_BackendKt_chatParseMarkdown(JNIEnv *env, jclass clazz, jstring str) { +Java_chat_simplex_common_platform_CoreKt_chatParseMarkdown(JNIEnv *env, jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_parse_markdown(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); @@ -80,7 +80,7 @@ Java_chat_simplex_common_platform_BackendKt_chatParseMarkdown(JNIEnv *env, jclas } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_BackendKt_chatParseServer(JNIEnv *env, jclass clazz, jstring str) { +Java_chat_simplex_common_platform_CoreKt_chatParseServer(JNIEnv *env, jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_parse_server(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); @@ -88,7 +88,7 @@ Java_chat_simplex_common_platform_BackendKt_chatParseServer(JNIEnv *env, jclass } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_BackendKt_chatPasswordHash(JNIEnv *env, jclass clazz, jstring pwd, jstring salt) { +Java_chat_simplex_common_platform_CoreKt_chatPasswordHash(JNIEnv *env, jclass clazz, jstring pwd, jstring salt) { const char *_pwd = (*env)->GetStringUTFChars(env, pwd, JNI_FALSE); const char *_salt = (*env)->GetStringUTFChars(env, salt, JNI_FALSE); jstring res = (*env)->NewStringUTF(env, chat_password_hash(_pwd, _salt)); diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt similarity index 77% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/App.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 2079abed0c..aee62177a8 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -1,6 +1,5 @@ -package chat.simplex.app +package chat.simplex.common -import android.os.SystemClock import androidx.compose.animation.core.Animatable import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -10,44 +9,44 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.SimpleButton -import chat.simplex.app.views.SplashView -import chat.simplex.app.views.call.ActiveCallView -import chat.simplex.app.views.call.IncomingCallAlertView -import chat.simplex.app.views.chat.ChatView -import chat.simplex.app.views.chatlist.ChatListView -import chat.simplex.app.views.chatlist.ShareListView -import chat.simplex.app.views.database.DatabaseErrorView -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.* -import chat.simplex.app.views.usersettings.LAMode -import chat.simplex.app.views.usersettings.laUnavailableInstructionAlert +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.SimpleButton +import chat.simplex.common.views.SplashView +import chat.simplex.common.views.call.ActiveCallView +import chat.simplex.common.views.call.IncomingCallAlertView +import chat.simplex.common.views.chat.ChatView +import chat.simplex.common.views.chatlist.ChatListView +import chat.simplex.common.views.chatlist.ShareListView +import chat.simplex.common.views.database.DatabaseErrorView +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.* +import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.launch @Composable -fun MainPage( - chatModel: ChatModel, - userAuthorized: MutableState, - laFailed: MutableState, - destroyedAfterBackPress: MutableState, - runAuthenticate: () -> Unit, - setPerformLA: (Boolean) -> Unit, - showLANotice: () -> Unit -) { +fun AppScreen() { + ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { + Surface(color = MaterialTheme.colors.background) { + MainScreen() + } + } +} + +@Composable +fun MainScreen() { + val chatModel = ChatModel var showChatDatabaseError by rememberSaveable { mutableStateOf(chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null) } LaunchedEffect(chatModel.chatDbStatus.value) { showChatDatabaseError = chatModel.chatDbStatus.value != DBMigrationResult.OK && chatModel.chatDbStatus.value != null } - var showAdvertiseLAAlert by remember { mutableStateOf(false) } LaunchedEffect(showAdvertiseLAAlert) { if ( @@ -57,8 +56,7 @@ fun MainPage( && chatModel.chats.isNotEmpty() && chatModel.activeCallInvitation.value == null ) { - showLANotice() - } + AppLock.showLANotice(ChatModel.controller.appPrefs.laNoticeShown) } } LaunchedEffect(chatModel.showAdvertiseLAUnavailableAlert.value) { if (chatModel.showAdvertiseLAUnavailableAlert.value) { @@ -83,8 +81,8 @@ fun MainPage( stringResource(MR.strings.auth_unlock), icon = painterResource(MR.images.ic_lock), click = { - laFailed.value = false - runAuthenticate() + AppLock.laFailed.value = false + AppLock.runAuthenticate() } ) } @@ -117,7 +115,7 @@ fun MainPage( ) { val stopped = chatModel.chatRunning.value == false if (chatModel.sharedContent.value == null) - ChatListView(chatModel, setPerformLA, stopped) + ChatListView(chatModel, AppLock::setPerformLA, stopped) else ShareListView(chatModel, stopped) } @@ -143,7 +141,7 @@ fun MainPage( } } } - Box (Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@ { + Box(Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@{ currentChatId?.let { ChatView(it, chatModel, onComposed) } @@ -157,22 +155,23 @@ fun MainPage( onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } ModalManager.shared.showInView() - val unauthorized = remember { derivedStateOf { userAuthorized.value != true } } + + val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } } if (unauthorized.value && !(chatModel.activeCallViewIsVisible.value && chatModel.showCallView.value)) { LaunchedEffect(Unit) { // With these constrains when user presses back button while on ChatList, activity destroys and shows auth request // while the screen moves to a launcher. Detect it and prevent showing the auth - if (!(destroyedAfterBackPress.value && chatModel.controller.appPrefs.laMode.get() == LAMode.SYSTEM)) { - runAuthenticate() + if (!(AppLock.destroyedAfterBackPress.value && chatModel.controller.appPrefs.laMode.get() == LAMode.SYSTEM)) { + AppLock.runAuthenticate() } } - if (chatModel.controller.appPrefs.performLA.get() && laFailed.value) { + if (chatModel.controller.appPrefs.performLA.get() && AppLock.laFailed.value) { AuthView() } else { SplashView() } } else if (chatModel.showCallView.value) { - ActiveCallView(chatModel) + ActiveCallView() } ModalManager.shared.showPasscodeInView() val invitation = chatModel.activeCallInvitation.value @@ -191,13 +190,13 @@ fun MainPage( // When using lock delay = 0 and screen rotates, the app will be locked which is not useful. // Let's prolong the unlocked period to 3 sec for screen rotation to take place if (chatModel.controller.appPrefs.laLockDelay.get() == 0) { - AppLock.enteredBackground.value = SystemClock.elapsedRealtime() + 3000 + AppLock.enteredBackground.value = AppLock.elapsedRealtime() + 3000 } } } @Composable -private fun InitializationView() { +fun InitializationView() { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Column(horizontalAlignment = Alignment.CenterHorizontally) { CircularProgressIndicator( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/AppLock.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/AppLock.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt index 7cd680592f..c66eeefd59 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/AppLock.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt @@ -1,16 +1,16 @@ -package chat.simplex.app +package chat.simplex.common -import android.util.Log import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.SharedPreference -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.localauth.SetAppPasscodeView -import chat.simplex.app.views.usersettings.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.Log +import chat.simplex.common.platform.TAG +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.localauth.SetAppPasscodeView +import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import kotlinx.coroutines.* @@ -70,8 +70,8 @@ object AppLock { private fun initialEnableLA() { val m = ChatModel - val appPrefs = m.controller.appPrefs - m.controller.appPrefs.laMode.set(LAMode.SYSTEM) + val appPrefs = ChatController.appPrefs + appPrefs.laMode.set(LAMode.SYSTEM) authenticate( generalGetString(MR.strings.auth_enable_simplex_lock), generalGetString(MR.strings.auth_confirm_credential), @@ -99,19 +99,18 @@ object AppLock { } private fun setPasscode() { - val chatModel = ChatModel - val appPrefs = chatModel.controller.appPrefs + val appPrefs = ChatController.appPrefs ModalManager.shared.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( submit = { - chatModel.performLA.value = true + ChatModel.performLA.value = true appPrefs.performLA.set(true) appPrefs.laMode.set(LAMode.PASSCODE) laTurnedOnAlert() }, cancel = { - chatModel.performLA.value = false + ChatModel.performLA.value = false appPrefs.performLA.set(false) laPasscodeNotSetAlert() }, @@ -122,7 +121,7 @@ object AppLock { } fun setAuthState() { - userAuthorized.value = !ChatModel.controller.appPrefs.performLA.get() + userAuthorized.value = !ChatController.appPrefs.performLA.get() } fun runAuthenticate() { @@ -169,7 +168,7 @@ object AppLock { } fun setPerformLA(on: Boolean) { - ChatModel.controller.appPrefs.laNoticeShown.set(true) + ChatController.appPrefs.laNoticeShown.set(true) if (on) { enableLA() } else { @@ -249,4 +248,22 @@ object AppLock { } ) } + + fun elapsedRealtime(): Long = System.nanoTime() / 1_000_000 + + fun recheckAuthState() { + val enteredBackgroundVal = enteredBackground.value + val delay = ChatController.appPrefs.laLockDelay.get() + if (enteredBackgroundVal == null || elapsedRealtime() - enteredBackgroundVal >= delay * 1000) { + if (userAuthorized.value != false) { + /** [runAuthenticate] will be called in [MainScreen] if needed. Making like this prevents double showing of passcode on start */ + setAuthState() + } else if (!ChatModel.activeCallViewIsVisible.value) { + runAuthenticate() + } + } + } + fun appWasHidden() { + enteredBackground.value = elapsedRealtime() + } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/model/ChatModel.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 2ad06648c5..00a1408747 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1,19 +1,17 @@ -package chat.simplex.app.model +package chat.simplex.common.model -import android.net.Uri import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.* import androidx.compose.ui.text.style.TextDecoration -import chat.simplex.app.R -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.call.* -import chat.simplex.app.views.chat.ComposeState -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.OnboardingStage -import chat.simplex.app.views.usersettings.NotificationPreviewMode +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.call.* +import chat.simplex.common.views.chat.ComposeState +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.common.platform.AudioPlayer import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource @@ -26,6 +24,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* import java.io.File +import java.net.URI import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.util.* @@ -65,14 +64,21 @@ object ChatModel { val clearOverlays = mutableStateOf(false) // set when app is opened via contact or invitation URI - val appOpenUrl = mutableStateOf(null) + val appOpenUrl = mutableStateOf(null) // preferences - val notificationsMode by lazy { mutableStateOf(NotificationsMode.values().firstOrNull { it.name == controller.appPrefs.notificationsMode.get() } ?: NotificationsMode.default) } - val notificationPreviewMode by lazy { mutableStateOf(NotificationPreviewMode.values().firstOrNull { it.name == controller.appPrefs.notificationPreviewMode.get() } ?: NotificationPreviewMode.default) } - val performLA by lazy { mutableStateOf(controller.appPrefs.performLA.get()) } + val notificationPreviewMode by lazy { + mutableStateOf( + try { + NotificationPreviewMode.valueOf(controller.appPrefs.notificationPreviewMode.get()!!) + } catch (e: Exception) { + NotificationPreviewMode.default + } + ) + } + val performLA by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) } val showAdvertiseLAUnavailableAlert = mutableStateOf(false) - val incognito by lazy { mutableStateOf(controller.appPrefs.incognito.get()) } + val incognito by lazy { mutableStateOf(ChatController.appPrefs.incognito.get()) } // current WebRTC call val callManager = CallManager(this) @@ -94,7 +100,7 @@ object ChatModel { val sharedContent = mutableStateOf(null as SharedContent?) val filesToDelete = mutableSetOf() - val simplexLinkMode by lazy { mutableStateOf(controller.appPrefs.simplexLinkMode.get()) } + val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) } fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) { currentUser.value @@ -2409,3 +2415,11 @@ data class ChatItemVersion( val itemVersionTs: Instant, val createdAt: Instant, ) + +enum class NotificationPreviewMode { + MESSAGE, CONTACT, HIDDEN; + + companion object { + val default: NotificationPreviewMode = MESSAGE + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/model/SimpleXAPI.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 737344be1f..279f84f85b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1,23 +1,21 @@ -package chat.simplex.app.model +package chat.simplex.common.model -import android.content.* -import android.os.Build -import android.util.Log -import chat.simplex.app.views.helpers.* +import chat.simplex.common.views.helpers.* import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import dev.icerock.moko.resources.compose.painterResource -import chat.simplex.app.* -import chat.simplex.app.platform.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.call.* -import chat.simplex.app.views.newchat.ConnectViaLinkTab -import chat.simplex.app.views.onboarding.OnboardingStage -import chat.simplex.app.views.usersettings.* +import chat.simplex.common.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.call.* +import chat.simplex.common.views.newchat.ConnectViaLinkTab +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.common.views.usersettings.* import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration import chat.simplex.res.MR +import com.russhwolf.settings.Settings import kotlinx.coroutines.* import kotlinx.datetime.Clock import kotlinx.datetime.Instant @@ -45,19 +43,17 @@ enum class SimplexLinkMode { BROWSER; companion object { - val default = SimplexLinkMode.DESCRIPTION + val default = DESCRIPTION } } class AppPreferences { - private val sharedPreferences: SharedPreferences = SimplexApp.context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) - private val sharedPreferencesThemes: SharedPreferences = SimplexApp.context.getSharedPreferences(SHARED_PREFS_THEMES_ID, Context.MODE_PRIVATE) - // deprecated, remove in 2024 private val runServiceInBackground = mkBoolPreference(SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND, true) - val notificationsMode = mkStrPreference(SHARED_PREFS_NOTIFICATIONS_MODE, - if (!runServiceInBackground.get()) NotificationsMode.OFF.name else NotificationsMode.default.name - ) + val notificationsMode = mkEnumPreference( + SHARED_PREFS_NOTIFICATIONS_MODE, + if (!runServiceInBackground.get()) NotificationsMode.OFF else NotificationsMode.default + ) { NotificationsMode.values().firstOrNull { it.name == this } } val notificationPreviewMode = mkStrPreference(SHARED_PREFS_NOTIFICATION_PREVIEW_MODE, NotificationPreviewMode.default.name) val backgroundServiceNoticeShown = mkBoolPreference(SHARED_PREFS_SERVICE_NOTICE_SHOWN, false) val backgroundServiceBatteryNoticeShown = mkBoolPreference(SHARED_PREFS_SERVICE_BATTERY_NOTICE_SHOWN, false) @@ -143,7 +139,7 @@ class AppPreferences { val initializationVectorAppPassphrase = mkStrPreference(SHARED_PREFS_INITIALIZATION_VECTOR_APP_PASSPHRASE, null) val encryptedSelfDestructPassphrase = mkStrPreference(SHARED_PREFS_ENCRYPTED_SELF_DESTRUCT_PASSPHRASE, null) val initializationVectorSelfDestructPassphrase = mkStrPreference(SHARED_PREFS_INITIALIZATION_VECTOR_SELF_DESTRUCT_PASSPHRASE, null) - val encryptionStartedAt = mkDatePreference(SHARED_PREFS_ENCRYPTION_STARTED_AT, null, true) + val encryptionStartedAt = mkDatePreference(SHARED_PREFS_ENCRYPTION_STARTED_AT, null) val confirmDBUpgrades = mkBoolPreference(SHARED_PREFS_CONFIRM_DB_UPGRADES, false) val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false) val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null) @@ -154,7 +150,7 @@ class AppPreferences { json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) }, decode = { json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) - }, sharedPreferencesThemes) + }, settingsThemes) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0) @@ -162,65 +158,73 @@ class AppPreferences { private fun mkIntPreference(prefName: String, default: Int) = SharedPreference( - get = fun() = sharedPreferences.getInt(prefName, default), - set = fun(value) = sharedPreferences.edit().putInt(prefName, value).apply() + get = fun() = settings.getInt(prefName, default), + set = fun(value) = settings.putInt(prefName, value) ) private fun mkLongPreference(prefName: String, default: Long) = SharedPreference( - get = fun() = sharedPreferences.getLong(prefName, default), - set = fun(value) = sharedPreferences.edit().putLong(prefName, value).apply() + get = fun() = settings.getLong(prefName, default), + set = fun(value) = settings.putLong(prefName, value) ) private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): SharedPreference { val d = if (networkUseSocksProxy.get()) proxyDefault else default return SharedPreference( - get = fun() = sharedPreferences.getLong(prefName, d), - set = fun(value) = sharedPreferences.edit().putLong(prefName, value).apply() + get = fun() = settings.getLong(prefName, d), + set = fun(value) = settings.putLong(prefName, value) ) } private fun mkBoolPreference(prefName: String, default: Boolean) = SharedPreference( - get = fun() = sharedPreferences.getBoolean(prefName, default), - set = fun(value) = sharedPreferences.edit().putBoolean(prefName, value).apply() + get = fun() = settings.getBoolean(prefName, default), + set = fun(value) = settings.putBoolean(prefName, value) ) private fun mkStrPreference(prefName: String, default: String?): SharedPreference = SharedPreference( - get = fun() = sharedPreferences.getString(prefName, default), - set = fun(value) = sharedPreferences.edit().putString(prefName, value).apply() + get = { + val nullValue = "----------------------" + val pref = settings.getString(prefName, default ?: nullValue) + if (pref != nullValue) { + pref + } else { + null + } + }, + set = fun(value) = if (value != null) settings.putString(prefName, value) else settings.remove(prefName) ) private fun mkEnumPreference(prefName: String, default: T, construct: String.() -> T?): SharedPreference = SharedPreference( - get = fun() = sharedPreferences.getString(prefName, default.toString())?.construct() ?: default, - set = fun(value) = sharedPreferences.edit().putString(prefName, value.toString()).apply() + get = fun() = settings.getString(prefName, default.toString()).construct() ?: default, + set = fun(value) = settings.putString(prefName, value.toString()) ) - /** - * Provide `[commit] = true` to save preferences right now, not after some unknown period of time. - * So in case of a crash this value will be saved 100% - * */ - private fun mkDatePreference(prefName: String, default: Instant?, commit: Boolean = false): SharedPreference = + // LALAL + private fun mkDatePreference(prefName: String, default: Instant?): SharedPreference = SharedPreference( get = { - val pref = sharedPreferences.getString(prefName, default?.toEpochMilliseconds()?.toString()) - pref?.let { Instant.fromEpochMilliseconds(pref.toLong()) } + val nullValue = "----------------------" + val pref = settings.getString(prefName, default?.toEpochMilliseconds()?.toString() ?: nullValue) + if (pref != nullValue) { + Instant.fromEpochMilliseconds(pref.toLong()) + } else { + null + } }, - set = fun(value) = sharedPreferences.edit().putString(prefName, value?.toEpochMilliseconds()?.toString()).let { - if (commit) it.commit() else it.apply() - } + set = fun(value) = if (value?.toEpochMilliseconds() != null) settings.putString(prefName, value.toEpochMilliseconds().toString()) else settings.remove(prefName) ) - private fun mkMapPreference(prefName: String, default: Map, encode: (Map) -> String, decode: (String) -> Map, prefs: SharedPreferences = sharedPreferences): SharedPreference> = + private fun mkMapPreference(prefName: String, default: Map, encode: (Map) -> String, decode: (String) -> Map, prefs: Settings = settings): SharedPreference> = SharedPreference( - get = fun() = decode(prefs.getString(prefName, encode(default))!!), - set = fun(value) = prefs.edit().putString(prefName, encode(value)).apply() + get = fun() = decode(prefs.getString(prefName, encode(default))), + set = fun(value) = prefs.putString(prefName, encode(value)) ) companion object { - internal const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS" + const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS" internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES" private const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" private const val SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND = "RunServiceInBackground" @@ -240,7 +244,7 @@ class AppPreferences { private const val SHARED_PREFS_PRIVACY_TRANSFER_IMAGES_INLINE = "PrivacyTransferImagesInline" private const val SHARED_PREFS_PRIVACY_LINK_PREVIEWS = "PrivacyLinkPreviews" private const val SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE = "PrivacySimplexLinkMode" - internal const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup" + const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup" private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls" private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites" private const val SHARED_PREFS_CHAT_ARCHIVE_NAME = "ChatArchiveName" @@ -294,7 +298,6 @@ private const val MESSAGE_TIMEOUT: Int = 15_000_000 object ChatController { var ctrl: ChatCtrl? = -1 val appPrefs: AppPreferences by lazy { AppPreferences() } - val ntfManager by lazy { NtfManager } val chatModel = ChatModel private var receiverStarted = false @@ -328,7 +331,7 @@ object ChatController { chatModel.userCreated.value = true apiSetIncognito(chatModel.incognito.value) getUserChatData() - chatModel.controller.appPrefs.chatLastStart.set(Clock.System.now()) + appPrefs.chatLastStart.set(Clock.System.now()) chatModel.chatRunning.value = true startReceiver() Log.d(TAG, "startChat: started") @@ -940,7 +943,8 @@ object ChatController { val r = sendCmd(CC.ApiShowMyAddress(userId)) if (r is CR.UserContactLink) return r.contactLink if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore - && r.chatError.storeError is StoreError.UserContactLinkNotFound) { + && r.chatError.storeError is StoreError.UserContactLinkNotFound + ) { return null } Log.e(TAG, "apiGetUserAddress bad response: ${r.responseType} ${r.details}") @@ -952,7 +956,8 @@ object ChatController { val r = sendCmd(CC.ApiAddressAutoAccept(userId, autoAccept)) if (r is CR.UserContactLinkUpdated) return r.contactLink if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore - && r.chatError.storeError is StoreError.UserContactLinkNotFound) { + && r.chatError.storeError is StoreError.UserContactLinkNotFound + ) { return null } Log.e(TAG, "userAddressAutoAccept bad response: ${r.responseType} ${r.details}") @@ -1381,7 +1386,7 @@ object ChatController { || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) { withApi { receiveFile(r.user, file.fileId) } } - if (cItem.showNotification && (!isAppOnForeground || chatModel.chatId.value != cInfo.id)) { + if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id)) { ntfManager.notifyMessageReceived(r.user, cInfo, cItem) } } @@ -1534,7 +1539,7 @@ object ChatController { // TODO check encryption is compatible withCall(r, r.contact) { call -> chatModel.activeCall.value = call.copy(callState = CallState.OfferReceived, peerMedia = r.callType.media, sharedKey = r.sharedKey) - val useRelay = chatModel.controller.appPrefs.webrtcPolicyRelay.get() + val useRelay = appPrefs.webrtcPolicyRelay.get() val iceServers = getIceServers() Log.d(TAG, ".callOffer iceServers $iceServers") chatModel.callCommand.value = WCallCommand.Offer( @@ -3836,11 +3841,9 @@ sealed class ArchiveError { @Serializable @SerialName("importFile") class ArchiveErrorImportFile(val file: String, val chatError: ChatError): ArchiveError() } -enum class NotificationsMode(private val requiresIgnoringBatterySinceSdk: Int) { - OFF(Int.MAX_VALUE), PERIODIC(Build.VERSION_CODES.M), SERVICE(Build.VERSION_CODES.S), /*INSTANT(Int.MAX_VALUE) - for Firebase notifications */; - val requiresIgnoringBattery - get() = requiresIgnoringBatterySinceSdk <= Build.VERSION.SDK_INT +enum class NotificationsMode() { + OFF, PERIODIC, SERVICE, /*INSTANT - for Firebase notifications */; companion object { val default: NotificationsMode = SERVICE diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt new file mode 100644 index 0000000000..6e410cc2c3 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -0,0 +1,47 @@ +package chat.simplex.common.platform + +import chat.simplex.common.BuildConfigCommon +import chat.simplex.common.model.ChatController +import chat.simplex.common.ui.theme.DefaultTheme +import java.util.* + +enum class AppPlatform { + ANDROID, DESKTOP; + + val isAndroid: Boolean + get() = this == ANDROID +} + +expect val appPlatform: AppPlatform + +val appVersionInfo: Pair = if (appPlatform == AppPlatform.ANDROID) + BuildConfigCommon.ANDROID_VERSION_NAME to BuildConfigCommon.ANDROID_VERSION_CODE +else + BuildConfigCommon.DESKTOP_VERSION_NAME to null + +expect fun initHaskell() + +class FifoQueue(private var capacity: Int) : LinkedList() { + override fun add(element: E): Boolean { + if(size > capacity) removeFirst() + return super.add(element) + } +} + +// LALAL VERSION CODE +fun runMigrations() { + val lastMigration = ChatController.appPrefs.lastMigratedVersionCode + if (lastMigration.get() < BuildConfigCommon.ANDROID_VERSION_CODE) { + while (true) { + if (lastMigration.get() < 117) { + if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.DARK.name) { + ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name) + } + lastMigration.set(117) + } else { + lastMigration.set(BuildConfigCommon.ANDROID_VERSION_CODE) + break + } + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Back.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Back.kt new file mode 100644 index 0000000000..6096cc3952 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Back.kt @@ -0,0 +1,7 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.* + +@SuppressWarnings("MissingJvmstatic") +@Composable +expect fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Backend.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt similarity index 81% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Backend.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index c4e6d043c8..8762c1aecd 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/Backend.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -1,11 +1,8 @@ -package chat.simplex.app.platform +package chat.simplex.common.platform -import android.util.Log -import chat.simplex.app.SimplexService -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.OnboardingStage +import chat.simplex.common.model.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage import kotlinx.serialization.decodeFromString // ghc's rts @@ -31,7 +28,6 @@ val appPreferences: AppPreferences val chatController: ChatController = ChatController - suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) { val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() val dbAbsolutePathPrefix = getFilesDirectory() @@ -65,12 +61,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat savedOnboardingStage } chatController.startChat(user) - // Prevents from showing "Enable notifications" alert when onboarding wasn't complete yet - if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) { - SimplexService.showBackgroundServiceNoticeIfNeeded() - if (appPreferences.notificationsMode.get() == NotificationsMode.SERVICE.name) - SimplexService.start() - } + platform.androidChatInitializedAndStarted() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Cryptor.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Cryptor.kt new file mode 100644 index 0000000000..5af941fb42 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Cryptor.kt @@ -0,0 +1,9 @@ +package chat.simplex.common.platform + +interface CryptorInterface { + fun decryptData(data: ByteArray, iv: ByteArray, alias: String): String? + fun encryptText(text: String, alias: String): Pair + fun deleteKey(alias: String) +} + +expect val cryptor: CryptorInterface diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt new file mode 100644 index 0000000000..7cfda123c6 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -0,0 +1,80 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.Composable +import chat.simplex.common.model.CIFile +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.res.MR +import java.io.* +import java.net.URI + +expect val dataDir: File +expect val tmpDir: File +expect val cacheDir: File + +fun copyFileToFile(from: File, to: URI, finally: () -> Unit) { + try { + to.outputStream().use { stream -> + BufferedOutputStream(stream).use { outputStream -> + from.inputStream().use { it.copyTo(outputStream) } + } + } + showToast(generalGetString(MR.strings.file_saved)) + } catch (e: Error) { + showToast(generalGetString(MR.strings.error_saving_file)) + Log.e(TAG, "copyFileToFile error saving file $e") + } finally { + finally() + } +} + +fun copyBytesToFile(bytes: ByteArrayInputStream, to: URI, finally: () -> Unit) { + try { + to.outputStream().use { stream -> + BufferedOutputStream(stream).use { outputStream -> + bytes.use { it.copyTo(outputStream) } + } + } + showToast(generalGetString(MR.strings.file_saved)) + } catch (e: Error) { + showToast(generalGetString(MR.strings.error_saving_file)) + Log.e(TAG, "copyBytesToFile error saving file $e") + } finally { + finally() + } +} + +fun getFilesDirectory(): String { + return dataDir.absolutePath + File.separator + "files" +} + +// LALAL +fun getTempFilesDirectory(): String { + return getFilesDirectory() + File.separator + "temp_files" +} + +fun getAppFilesDirectory(): String { + return getFilesDirectory() + File.separator + "app_files" +} + +fun getAppFilePath(fileName: String): String { + return getAppFilesDirectory() + File.separator + fileName +} + +fun getLoadedFilePath(file: CIFile?): String? { + return if (file?.filePath != null && file.loaded) { + val filePath = getAppFilePath(file.filePath) + if (File(filePath).exists()) filePath else null + } else { + null + } +} + +@Composable +expect fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher + +expect class FileChooserLauncher() { + suspend fun launch(input: String) +} + +expect fun URI.inputStream(): InputStream? +expect fun URI.outputStream(): OutputStream diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt new file mode 100644 index 0000000000..e73786b2e1 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt @@ -0,0 +1,24 @@ +package chat.simplex.common.platform + +import androidx.compose.ui.graphics.ImageBitmap +import boofcv.struct.image.GrayU8 +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.net.URI + +expect fun base64ToBitmap(base64ImageString: String): ImageBitmap +expect fun resizeImageToStrSize(image: ImageBitmap, maxDataSize: Long): String +expect fun resizeImageToDataSize(image: ImageBitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream +expect fun cropToSquare(image: ImageBitmap): ImageBitmap +expect fun compressImageStr(bitmap: ImageBitmap): String +expect fun compressImageData(bitmap: ImageBitmap, usePng: Boolean): ByteArrayOutputStream + +expect fun GrayU8.toImageBitmap(): ImageBitmap + +expect fun ImageBitmap.addLogo(): ImageBitmap +expect fun ImageBitmap.scale(width: Int, height: Int): ImageBitmap + +expect fun isImage(uri: URI): Boolean +expect fun isAnimImage(uri: URI, drawable: Any?): Boolean + +expect fun loadImageBitmap(inputStream: InputStream): ImageBitmap diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Log.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Log.kt new file mode 100644 index 0000000000..a1b39527d1 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Log.kt @@ -0,0 +1,10 @@ +package chat.simplex.common.platform + +const val TAG = "SIMPLEX" + +expect object Log { + fun d(tag: String, text: String) + fun e(tag: String, text: String) + fun i(tag: String, text: String) + fun w(tag: String, text: String) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt new file mode 100644 index 0000000000..a5c455d989 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt @@ -0,0 +1,13 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +expect fun Modifier.navigationBarsWithImePadding(): Modifier + +@Composable +expect fun ProvideWindowInsets( + consumeWindowInsets: Boolean = true, + windowInsetsAnimationsEnabled: Boolean = true, + content: @Composable () -> Unit +) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Notifications.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Notifications.kt new file mode 100644 index 0000000000..14d6f1701e --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Notifications.kt @@ -0,0 +1,3 @@ +package chat.simplex.common.platform + +expect fun allowedToShowNotification(): Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt new file mode 100644 index 0000000000..1a722950ce --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt @@ -0,0 +1,23 @@ +package chat.simplex.common.platform + +import chat.simplex.common.model.* +import chat.simplex.common.views.call.RcvCallInvitation + +enum class NotificationAction { + ACCEPT_CONTACT_REQUEST +} + +lateinit var ntfManager: NtfManager + +abstract class NtfManager { + abstract fun notifyContactConnected(user: User, contact: Contact) + abstract fun notifyContactRequestReceived(user: User, cInfo: ChatInfo.ContactRequest) + abstract fun notifyMessageReceived(user: User, cInfo: ChatInfo, cItem: ChatItem) + abstract fun notifyCallInvitation(invitation: RcvCallInvitation) + abstract fun hasNotificationsForChat(chatId: String): Boolean + abstract fun cancelNotificationsForChat(chatId: String) + abstract fun displayNotification(user: User, chatId: String, displayName: String, msgText: String, image: String? = null, actions: List = emptyList()) + abstract fun createNtfChannelsMaybeShowAlert() + abstract fun cancelCallNotification() + abstract fun cancelAllNotifications() +} 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 new file mode 100644 index 0000000000..eeea2d08fc --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -0,0 +1,26 @@ +package chat.simplex.common.platform + +import chat.simplex.common.model.NotificationsMode + +interface PlatformInterface { + suspend fun androidServiceStart() {} + fun androidServiceSafeStop() {} + fun androidNotificationsModeChanged(mode: NotificationsMode) {} + fun androidChatStartedAfterBeingOff() {} + fun androidChatStopped() {} + fun androidChatInitializedAndStarted() {} +} +/** + * Multiplatform project has separate directories per platform + common directory that contains directories per platform + common for all of them. + * This means that we can not call code from `android` directory via code from `common/androidMain` directory. So this is a way to do it: + * - we specify interface that should be implemented by platforms + * - platforms made its implementation by assigning it to this variable at runtime + * - common code calls this variable and everything works as expected. + * + * Functions that expected to be used on only one platform, should be prefixed with platform name, like androidSomething. It helps + * to identify it's use-case. Easy to understand that it is only needed on one specific platform. Functions without prefixes are used on + * more than one platform. + * + * See [SimplexApp] and [AppCommon.desktop] for re-assigning of this var + * */ +var platform: PlatformInterface = object : PlatformInterface {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt new file mode 100644 index 0000000000..6d60703e53 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt @@ -0,0 +1,15 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.text.TextStyle +import chat.simplex.common.views.chat.ComposeState + +@Composable +expect fun PlatformTextField( + composeState: MutableState, + textStyle: MutableState, + showDeleteTextButton: MutableState, + userIsObserver: Boolean, + onMessageChange: (String) -> Unit +) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt new file mode 100644 index 0000000000..be2c87a2fc --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt @@ -0,0 +1,42 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.MutableState +import chat.simplex.common.model.ChatItem +import kotlinx.coroutines.CoroutineScope + +interface RecorderInterface { + companion object { + // Allows to stop the recorder from outside without having the recorder in a variable + var stopRecording: (() -> Unit)? = null + val extension: String = "m4a" + } + fun start(onProgressUpdate: (position: Int?, finished: Boolean) -> Unit): String + fun stop(): Int +} + +expect class RecorderNative: RecorderInterface + +interface AudioPlayerInterface { + fun play( + filePath: String?, + audioPlaying: MutableState, + progress: MutableState, + duration: MutableState, + resetOnEnd: Boolean, + ) + fun stop() + fun stop(item: ChatItem) + fun stop(fileName: String?) + fun pause(audioPlaying: MutableState, pro: MutableState) + fun seekTo(ms: Int, pro: MutableState, filePath: String?) + fun duration(filePath: String): Int? +} + +expect object AudioPlayer: AudioPlayerInterface + +interface SoundPlayerInterface { + fun start(scope: CoroutineScope, sound: Boolean) + fun stop() +} + +expect object SoundPlayer: SoundPlayerInterface diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt new file mode 100644 index 0000000000..3d31faaa84 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt @@ -0,0 +1,31 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import com.russhwolf.settings.Settings +import dev.icerock.moko.resources.StringResource + +@Composable +expect fun font(name: String, res: String, weight: FontWeight = FontWeight.Normal, style: FontStyle = FontStyle.Normal): Font + +expect fun StringResource.localized(): String + +// Non-@Composable implementation +expect fun isInNightMode(): Boolean + +expect val settings: Settings +expect val settingsThemes: Settings + +enum class ScreenOrientation { + UNDEFINED, PORTRAIT, LANDSCAPE +} + +expect fun screenOrientation(): ScreenOrientation + +@Composable +expect fun screenWidth(): Dp + +expect fun isRtl(text: CharSequence): Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt new file mode 100644 index 0000000000..03ad4b5441 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Share.kt @@ -0,0 +1,9 @@ +package chat.simplex.common.platform + +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.UriHandler + +expect fun UriHandler.sendEmail(subject: String, body: CharSequence) + +expect fun ClipboardManager.shareText(text: String) +expect fun shareFile(text: String, filePath: String) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/UI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/UI.kt new file mode 100644 index 0000000000..38629f038b --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/UI.kt @@ -0,0 +1,16 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.* +import chat.simplex.common.views.helpers.KeyboardState + +expect fun showToast(text: String, timeout: Long = 2500L) + +@Composable +expect fun LockToCurrentOrientationUntilDispose() + +@Composable +expect fun LocalMultiplatformView(): Any? + +@Composable +expect fun getKeyboardState(): State +expect fun hideKeyboard(view: Any?) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/VideoPlayer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/VideoPlayer.kt new file mode 100644 index 0000000000..bde9d8a49d --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/VideoPlayer.kt @@ -0,0 +1,37 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.MutableState +import androidx.compose.ui.graphics.ImageBitmap +import java.net.URI + +interface VideoPlayerInterface { + data class PreviewAndDuration(val preview: ImageBitmap?, val duration: Long?, val timestamp: Long) + + val soundEnabled: MutableState + val brokenVideo: MutableState + val videoPlaying: MutableState + val progress: MutableState + val duration: MutableState + val preview: MutableState + + fun stop() + fun play(resetOnEnd: Boolean) + fun enableSound(enable: Boolean): Boolean + fun release(remove: Boolean) +} + +expect class VideoPlayer: VideoPlayerInterface { + companion object { + fun getOrCreate( + uri: URI, + gallery: Boolean, + defaultPreview: ImageBitmap, + defaultDuration: Long, + soundEnabled: Boolean + ): VideoPlayer + fun enableSound(enable: Boolean, fileName: String?, gallery: Boolean): Boolean + fun release(uri: URI, gallery: Boolean, remove: Boolean) + fun stopAll() + fun releaseAll() + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Color.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Color.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt index 84686d7482..a01fc9de51 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Color.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.ui.theme +package chat.simplex.common.ui.theme import androidx.compose.ui.graphics.Color diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Shape.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Shape.kt similarity index 87% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Shape.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Shape.kt index 79ab4eead4..293257c095 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Shape.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Shape.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.ui.theme +package chat.simplex.common.ui.theme import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Shapes diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Theme.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index 75aa950d21..3f106bd729 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.ui.theme +package chat.simplex.common.ui.theme import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme @@ -8,13 +8,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.* import androidx.compose.ui.unit.dp -import chat.simplex.app.SimplexApp -import chat.simplex.app.platform.isInNightMode -import chat.simplex.app.views.helpers.* -import chat.simplex.res.MR +import chat.simplex.common.model.ChatController +import chat.simplex.common.platform.isInNightMode +import chat.simplex.common.views.helpers.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import chat.simplex.res.MR enum class DefaultTheme { SYSTEM, LIGHT, DARK, SIMPLEX; @@ -264,7 +264,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { } val systemDark = isSystemInDarkTheme() LaunchedEffect(systemDark) { - if (SimplexApp.context.chatModel.controller.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == systemDark) { + if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == systemDark) { // Change active colors from light to dark and back based on system theme ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, systemDark) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt index 03d4af64c6..17ff695e73 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt @@ -1,18 +1,20 @@ -package chat.simplex.app.ui.theme +package chat.simplex.common.ui.theme import androidx.compose.material.Colors import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import chat.simplex.app.R -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.AppPreferences -import chat.simplex.app.views.helpers.generalGetString +import androidx.compose.ui.text.font.FontFamily import chat.simplex.res.MR +import chat.simplex.common.model.AppPreferences +import chat.simplex.common.model.ChatController +import chat.simplex.common.views.helpers.generalGetString + +// https://github.com/rsms/inter +// I place it here because IDEA shows an error (but still works anyway) when this declaration inside Type.kt +expect val Inter: FontFamily object ThemeManager { - private val appPrefs: AppPreferences by lazy { - SimplexApp.context.chatModel.controller.appPrefs - } + private val appPrefs: AppPreferences = ChatController.appPrefs data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt index e20c7dbbde..9acfffb3ac 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/ui/theme/Type.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt @@ -1,8 +1,8 @@ -package chat.simplex.app.ui.theme +package chat.simplex.common.ui.theme import androidx.compose.material.Typography import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.font.* import androidx.compose.ui.unit.sp // Set of Material typography styles to start with diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/Preview.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/Preview.kt new file mode 100644 index 0000000000..e83db99520 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/Preview.kt @@ -0,0 +1,7 @@ +package androidx.compose.desktop.ui.tooling.preview + +@Retention(AnnotationRetention.SOURCE) +@Target( + AnnotationTarget.FUNCTION +) +annotation class Preview diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/SplashView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/SplashView.kt similarity index 94% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/SplashView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/SplashView.kt index 9491ea75e0..cf9a8dfb62 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/SplashView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/SplashView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views +package chat.simplex.common.views import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/TerminalView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 5b305d6a13..b2692d541a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -1,7 +1,6 @@ -package chat.simplex.app.views +package chat.simplex.common.views -import android.content.res.Configuration -import androidx.activity.compose.BackHandler +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* @@ -9,20 +8,18 @@ import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.* -import chat.simplex.app.views.helpers.* -import com.google.accompanist.insets.ProvideWindowInsets -import com.google.accompanist.insets.navigationBarsWithImePadding +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* @Composable fun TerminalView(chatModel: ChatModel, close: () -> Unit) { @@ -30,12 +27,12 @@ fun TerminalView(chatModel: ChatModel, close: () -> Unit) { BackHandler(onBack = { close() }) - TerminalLayout( - remember { chatModel.terminalItems }, - composeState, - sendCommand = { sendCommand(chatModel, composeState) }, - close - ) + TerminalLayout( + remember { chatModel.terminalItems }, + composeState, + sendCommand = { sendCommand(chatModel, composeState) }, + close + ) } private fun sendCommand(chatModel: ChatModel, composeState: MutableState) { @@ -118,7 +115,7 @@ fun TerminalLog(terminalItems: List) { onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } } val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed().toList() } } - val context = LocalContext.current + val clipboard = LocalClipboardManager.current LazyColumn(state = listState, reverseLayout = true) { items(reversedTerminalItems) { item -> Text( @@ -129,7 +126,7 @@ fun TerminalLog(terminalItems: List) { modifier = Modifier .fillMaxWidth() .clickable { - ModalManager.shared.showModal(endButtons = { ShareButton { shareText(item.details) } }) { + ModalManager.shared.showModal(endButtons = { ShareButton { clipboard.shareText(item.details) } }) { SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) { Text(item.details, modifier = Modifier.padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING)) } @@ -140,12 +137,11 @@ fun TerminalLog(terminalItems: List) { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewTerminalLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/WelcomeView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 57ca136cf0..bea2088093 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views +package chat.simplex.common.views import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -22,17 +22,14 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.Profile -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.OnboardingStage -import chat.simplex.app.views.onboarding.ReadableText -import com.google.accompanist.insets.navigationBarsWithImePadding +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.Profile +import chat.simplex.common.platform.navigationBarsWithImePadding +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt similarity index 83% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallManager.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index ae59623482..f6b3c899ae 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -1,9 +1,8 @@ -package chat.simplex.app.views.call +package chat.simplex.common.views.call -import android.util.Log -import chat.simplex.app.TAG -import chat.simplex.app.model.ChatModel -import chat.simplex.app.views.helpers.withApi +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.withApi import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes @@ -15,10 +14,10 @@ class CallManager(val chatModel: ChatModel) { if (invitation.user.showNotifications) { if (Clock.System.now() - invitation.callTs <= 3.minutes) { activeCallInvitation.value = invitation - controller.ntfManager.notifyCallInvitation(invitation) + ntfManager.notifyCallInvitation(invitation) } else { val contact = invitation.contact - controller.ntfManager.displayNotification(user = invitation.user, chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText) + ntfManager.displayNotification(user = invitation.user, chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText) } } } @@ -62,7 +61,7 @@ class CallManager(val chatModel: ChatModel) { callInvitations.remove(invitation.contact.id) if (invitation.contact.id == activeCallInvitation.value?.contact?.id) { activeCallInvitation.value = null - controller.ntfManager.cancelCallNotification() + ntfManager.cancelCallNotification() } } } @@ -88,7 +87,7 @@ class CallManager(val chatModel: ChatModel) { callInvitations.remove(invitation.contact.id) if (invitation.contact.id == activeCallInvitation.value?.contact?.id) { activeCallInvitation.value = null - controller.ntfManager.cancelCallNotification() + ntfManager.cancelCallNotification() } withApi { if (!controller.apiRejectCall(invitation.contact)) { @@ -101,7 +100,7 @@ class CallManager(val chatModel: ChatModel) { fun reportCallRemoteEnded(invitation: RcvCallInvitation) { if (chatModel.activeCallInvitation.value?.contact?.id == invitation.contact.id) { chatModel.activeCallInvitation.value = null - chatModel.controller.ntfManager.cancelCallNotification() + ntfManager.cancelCallNotification() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt new file mode 100644 index 0000000000..2f4ffbb836 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt @@ -0,0 +1,6 @@ +package chat.simplex.common.views.call + +import androidx.compose.runtime.Composable + +@Composable +expect fun ActiveCallView() diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallAlertView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallAlertView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt index 532ecda9c6..447236286f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallAlertView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt @@ -1,5 +1,6 @@ -package chat.simplex.app.views.call +package chat.simplex.common.views.call +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -10,34 +11,31 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.ProfileImage -import chat.simplex.app.views.usersettings.ProfilePreview +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.ProfileImage +import chat.simplex.common.views.usersettings.ProfilePreview +import chat.simplex.common.platform.ntfManager +import chat.simplex.common.platform.SoundPlayer import chat.simplex.res.MR import kotlinx.datetime.Clock @Composable fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) { val cm = chatModel.callManager - val cxt = LocalContext.current val scope = rememberCoroutineScope() - LaunchedEffect(true) { SoundPlayer.shared.start(scope, sound = !chatModel.showCallView.value) } - DisposableEffect(true) { onDispose { SoundPlayer.shared.stop() } } + LaunchedEffect(true) { SoundPlayer.start(scope, sound = !chatModel.showCallView.value) } + DisposableEffect(true) { onDispose { SoundPlayer.stop() } } IncomingCallAlertLayout( invitation, chatModel, rejectCall = { cm.endCall(invitation = invitation) }, ignoreCall = { chatModel.activeCallInvitation.value = null - chatModel.controller.ntfManager.cancelCallNotification() + ntfManager.cancelCallNotification() }, acceptCall = { cm.acceptIncomingCall(invitation = invitation) } ) @@ -114,7 +112,7 @@ fun PreviewIncomingCallAlertLayout() { sharedKey = null, callTs = Clock.System.now() ), - chatModel = SimplexApp.context.chatModel, + chatModel = ChatModel, rejectCall = {}, ignoreCall = {}, acceptCall = {} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/WebRTC.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index fbd35d71ad..3e14414fc7 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -1,9 +1,9 @@ -package chat.simplex.app.views.call +package chat.simplex.common.views.call import androidx.compose.runtime.Composable import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.model.* -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.model.* import chat.simplex.res.MR import kotlinx.datetime.Instant import kotlinx.serialization.SerialName diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index dca2a04e52..6cb7b4b478 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import InfoRow import InfoRowEllipsis @@ -8,8 +8,7 @@ import SectionItemView import SectionSpacer import SectionTextFooter import SectionView -import android.widget.Toast -import androidx.activity.compose.BackHandler +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions @@ -27,15 +26,13 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.* -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCode -import chat.simplex.app.views.usersettings.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCode +import chat.simplex.common.views.usersettings.* +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* @@ -135,7 +132,7 @@ fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> if (r) { chatModel.removeChat(chatInfo.id) chatModel.chatId.value = null - chatModel.controller.ntfManager.cancelNotificationsForChat(chatInfo.id) + ntfManager.cancelNotificationsForChat(chatInfo.id) close?.invoke() } } @@ -154,7 +151,7 @@ fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit val updatedChatInfo = chatModel.controller.apiClearChat(chatInfo.chatType, chatInfo.apiId) if (updatedChatInfo != null) { chatModel.clearChat(updatedChatInfo) - chatModel.controller.ntfManager.cancelNotificationsForChat(chatInfo.id) + ntfManager.cancelNotificationsForChat(chatInfo.id) close?.invoke() } } @@ -214,7 +211,8 @@ fun ChatInfoLayout( if (contact.contactLink != null) { SectionView(stringResource(MR.strings.address_section_title).uppercase()) { QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) - ShareAddressButton { shareText(contact.contactLink) } + val clipboard = LocalClipboardManager.current + ShareAddressButton { clipboard.shareText(contact.contactLink) } SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName)) } SectionDividerSpaced() @@ -397,7 +395,7 @@ fun SimplexServers(text: String, servers: List) { val clipboardManager: ClipboardManager = LocalClipboardManager.current InfoRowEllipsis(text, info) { clipboardManager.setText(AnnotatedString(servers.joinToString(separator = ","))) - Toast.makeText(SimplexApp.context, generalGetString(MR.strings.copied), Toast.LENGTH_SHORT).show() + showToast(generalGetString(MR.strings.copied)) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatItemInfoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index c0a63b6dd0..1701c6987c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import InfoRow import SectionBottomSpacer @@ -12,20 +12,21 @@ import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.platform.copyText -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.CurrentColors -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.chat.item.ItemAction -import chat.simplex.app.views.chat.item.MarkdownText -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.CurrentColors +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.chat.item.ItemAction +import chat.simplex.common.views.chat.item.MarkdownText +import chat.simplex.common.views.helpers.* +import chat.simplex.common.platform.shareText import chat.simplex.res.MR @Composable @@ -81,13 +82,15 @@ fun ChatItemInfoView(ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) { } } if (text != "") { + val clipboard = LocalClipboardManager.current DefaultDropdownMenu(showMenu) { ItemAction(stringResource(MR.strings.share_verb), painterResource(MR.images.ic_share), onClick = { - shareText(text) + clipboard.shareText(text) showMenu.value = false }) + val clipboard = LocalClipboardManager.current ItemAction(stringResource(MR.strings.copy_verb), painterResource(MR.images.ic_content_copy), onClick = { - copyText(text) + clipboard.setText(AnnotatedString(text)) showMenu.value = false }) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 1e8e31360f..b0846cc043 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1,10 +1,6 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat -import android.content.res.Configuration -import android.graphics.Bitmap -import android.net.Uri -import android.util.Log -import androidx.activity.compose.BackHandler +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.gestures.* import androidx.compose.foundation.layout.* @@ -26,26 +22,24 @@ import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.unit.* -import androidx.core.content.FileProvider -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.call.* -import chat.simplex.app.views.chat.group.* -import chat.simplex.app.views.chat.item.* -import chat.simplex.app.views.chatlist.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.helpers.AppBarHeight -import com.google.accompanist.insets.ProvideWindowInsets -import com.google.accompanist.insets.navigationBarsWithImePadding +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.call.* +import chat.simplex.common.views.chat.group.* +import chat.simplex.common.views.chat.item.* +import chat.simplex.common.views.chatlist.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.GroupInfo +import chat.simplex.common.platform.* +import chat.simplex.common.platform.AudioPlayer import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.datetime.Clock import java.io.File +import java.net.URI import kotlin.math.sign @Composable @@ -101,8 +95,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { .collect { activeChat.value = it } } } - val view = LocalView.current - val context = LocalContext.current + val view = LocalMultiplatformView() if (activeChat.value == null || user == null) { chatModel.chatId.value = null } else { @@ -114,6 +107,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatId }?.chatStats?.unreadCount ?: 0 } } + val clipboard = LocalClipboardManager.current ChatLayout( chat, @@ -279,7 +273,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { val ciInfo = chatModel.controller.apiGetChatItemInfo(cInfo.chatType, cInfo.apiId, cItem.id) if (ciInfo != null) { ModalManager.shared.showModal(endButtons = { ShareButton { - shareText(itemInfoShareText(cItem, ciInfo, chatModel.controller.appPrefs.developerTools.get())) + clipboard.shareText(itemInfoShareText(cItem, ciInfo, chatModel.controller.appPrefs.developerTools.get())) } }) { ChatItemInfoView(cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get()) } @@ -297,7 +291,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { }, markRead = { range, unreadCountAfter -> chatModel.markChatItemsRead(chat.chatInfo, range, unreadCountAfter) - chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id) + ntfManager.cancelNotificationsForChat(chat.id) withBGApi { chatModel.controller.apiChatRead( chat.chatInfo.chatType, @@ -422,7 +416,7 @@ fun ChatInfoToolbar( val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val menuItems = arrayListOf<@Composable () -> Unit>() menuItems.add { - ItemAction(stringResource(MR.strings.search_verb).capitalize(Locale.current), painterResource(MR.images.ic_search), onClick = { + ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = { showMenu.value = false showSearch = true }) @@ -602,8 +596,8 @@ fun BoxWithConstraintsScope.ChatItemsList( .distinctUntilChanged() .filter { !stopListening } .collect { - onComposed() - stopListening = true + onComposed() + stopListening = true } } DisposableEffectOnGone( @@ -966,8 +960,8 @@ private fun markUnreadChatAsRead(activeChat: MutableState, chatModel: Cha } sealed class ProviderMedia { - data class Image(val uri: Uri, val image: Bitmap): ProviderMedia() - data class Video(val uri: Uri, val preview: String): ProviderMedia() + data class Image(val uri: URI, val image: ImageBitmap): ProviderMedia() + data class Video(val uri: URI, val preview: String): ProviderMedia() } private fun providerForGallery( @@ -1004,17 +998,17 @@ private fun providerForGallery( val item = item(internalIndex, initialChatId)?.second ?: return null return when (item.content.msgContent) { is MsgContent.MCImage -> { - val imageBitmap: Bitmap? = getLoadedImage(item.file) + val imageBitmap: ImageBitmap? = getLoadedImage(item.file) val filePath = getLoadedFilePath(item.file) if (imageBitmap != null && filePath != null) { - val uri = FileProvider.getUriForFile(SimplexApp.context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) + val uri = getAppFileUri(filePath.substringAfterLast(File.separator)) ProviderMedia.Image(uri, imageBitmap) } else null } is MsgContent.MCVideo -> { val filePath = getLoadedFilePath(item.file) if (filePath != null) { - val uri = FileProvider.getUriForFile(SimplexApp.context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) + val uri = getAppFileUri(filePath.substringAfterLast(File.separator)) ProviderMedia.Video(uri, (item.content.msgContent as MsgContent.MCVideo).image) } else null } @@ -1058,12 +1052,11 @@ private fun ViewConfiguration.bigTouchSlop(slop: Float = 50f) = object: ViewConf override val touchSlop: Float get() = slop } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewChatLayout() { SimpleXTheme { @@ -1125,7 +1118,7 @@ fun PreviewChatLayout() { } } -@Preview(showBackground = true) +@Preview @Composable fun PreviewGroupChatLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeFileView.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeFileView.kt index 8043b7c8e8..83076f885b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeFileView.kt @@ -1,3 +1,6 @@ +package chat.simplex.common.views.chat + +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -7,10 +10,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.ui.theme.* +import chat.simplex.common.ui.theme.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeImageView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeImageView.kt index 6f8e89722a..906065f741 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeImageView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -10,15 +10,13 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.platform.base64ToBitmap -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.UploadContent +import chat.simplex.common.platform.base64ToBitmap import chat.simplex.res.MR +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.UploadContent @Composable fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) { @@ -38,7 +36,7 @@ fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Uni val content = media.content[index] if (content is UploadContent.Video) { Box(contentAlignment = Alignment.Center) { - val imageBitmap = base64ToBitmap(item).asImageBitmap() + val imageBitmap = base64ToBitmap(item) Image( imageBitmap, "preview video", @@ -53,7 +51,7 @@ fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Uni ) } } else { - val imageBitmap = base64ToBitmap(item).asImageBitmap() + val imageBitmap = base64ToBitmap(item) Image( imageBitmap, "preview image", diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index f5c47fc13b..c16a252a9c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -1,11 +1,6 @@ @file:UseSerializers(UriSerializer::class) -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat -import ComposeFileView -import ComposeVoiceView -import android.app.Activity -import android.graphics.* -import android.net.Uri import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* @@ -15,19 +10,20 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.graphics.ImageBitmap import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.model.* -import chat.simplex.app.platform.* -import chat.simplex.app.views.chat.item.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.item.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.serialization.* import java.io.File +import java.net.URI import java.nio.file.Files @Serializable @@ -36,7 +32,7 @@ sealed class ComposePreview { @Serializable class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview() @Serializable class MediaPreview(val images: List, val content: List): ComposePreview() @Serializable data class VoicePreview(val voice: String, val durationMs: Int, val finished: Boolean): ComposePreview() - @Serializable class FilePreview(val fileName: String, val uri: Uri): ComposePreview() + @Serializable class FilePreview(val fileName: String, val uri: URI): ComposePreview() } @Serializable @@ -151,6 +147,14 @@ fun chatItemPreview(chatItem: ChatItem): ComposePreview { } } +@Composable +expect fun AttachmentSelection( + composeState: MutableState, + attachmentOption: MutableState, + processPickedFile: (URI?, String?) -> Unit, + processPickedMedia: (List, String?) -> Unit +) + @Composable fun ComposeView( chatModel: ChatModel, @@ -159,7 +163,6 @@ fun ComposeView( attachmentOption: MutableState, showChooseAttachment: () -> Unit ) { - val context = LocalContext.current val linkUrl = rememberSaveable { mutableStateOf(null) } val prevLinkUrl = rememberSaveable { mutableStateOf(null) } val pendingLinkUrl = rememberSaveable { mutableStateOf(null) } @@ -168,11 +171,11 @@ fun ComposeView( val maxFileSize = getMaxFileSize(FileProtocol.XFTP) val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) val textStyle = remember { mutableStateOf(smallFont) } - val processPickedMedia = { uris: List, text: String? -> + val processPickedMedia = { uris: List, text: String? -> val content = ArrayList() val imagesPreview = ArrayList() uris.forEach { uri -> - var bitmap: Bitmap? = null + var bitmap: ImageBitmap? = null when { isImage(uri) -> { // Image @@ -210,7 +213,7 @@ fun ComposeView( composeState.value = composeState.value.copy(message = text ?: composeState.value.message, preview = ComposePreview.MediaPreview(imagesPreview, content)) } } - val processPickedFile = { uri: Uri?, text: String? -> + val processPickedFile = { uri: URI?, text: String? -> if (uri != null) { val fileSize = getFileSize(uri) if (fileSize != null && fileSize <= maxFileSize) { @@ -312,6 +315,8 @@ fun ComposeView( return null } + + suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? { val cInfo = chat.chatInfo val cs = composeState.value @@ -402,7 +407,7 @@ fun ComposeView( is ComposePreview.VoicePreview -> { val tmpFile = File(preview.voice) AudioPlayer.stop(tmpFile.absolutePath) - val actualFile = File(getAppFilePath(tmpFile.name.replaceAfter(RecorderNative.extension, ""))) + val actualFile = File(getAppFilePath(tmpFile.name.replaceAfter(RecorderInterface.extension, ""))) withContext(Dispatchers.IO) { Files.move(tmpFile.toPath(), actualFile.toPath()) } @@ -434,7 +439,7 @@ fun ComposeView( (cs.preview is ComposePreview.MediaPreview || cs.preview is ComposePreview.FilePreview || cs.preview is ComposePreview.VoicePreview) - ) { + ) { sent = send(cInfo, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) } } @@ -492,7 +497,7 @@ fun ComposeView( recState.value = RecordingState.NotStarted composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) withBGApi { - RecorderNative.stopRecording?.invoke() + RecorderInterface.stopRecording?.invoke() AudioPlayer.stop(filePath) filePath?.let { File(it).delete() } } @@ -699,32 +704,26 @@ fun ComposeView( } } - val activity = LocalContext.current as Activity - DisposableEffect(Unit) { - val orientation = activity.resources.configuration.orientation - onDispose { - if (orientation == activity.resources.configuration.orientation) { - val cs = composeState.value - if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) { - sendMessage(null) - resetLinkPreview() - clearCurrentDraft() - deleteUnusedFiles() - } else if (composeState.value.inProgress) { - clearCurrentDraft() - } else if (!composeState.value.empty) { - if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) { - composeState.value = cs.copy(preview = cs.preview.copy(finished = true)) - } - chatModel.draft.value = composeState.value - chatModel.draftChatId.value = chat.id - } else { - clearCurrentDraft() - deleteUnusedFiles() - } - chatModel.removeLiveDummy() + DisposableEffectOnGone { + val cs = composeState.value + if (cs.liveMessage != null && (cs.message.isNotEmpty() || cs.liveMessage.sent)) { + sendMessage(null) + resetLinkPreview() + clearCurrentDraft() + deleteUnusedFiles() + } else if (composeState.value.inProgress) { + clearCurrentDraft() + } else if (!composeState.value.empty) { + if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) { + composeState.value = cs.copy(preview = cs.preview.copy(finished = true)) } + chatModel.draft.value = composeState.value + chatModel.draftChatId.value = chat.id + } else { + clearCurrentDraft() + deleteUnusedFiles() } + chatModel.removeLiveDummy() } val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt index 6ea0b345f8..99d7de96be 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt @@ -1,4 +1,7 @@ +package chat.simplex.common.views.chat + import androidx.compose.animation.core.Animatable +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -12,13 +15,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.durationText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.durationText +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.platform.AudioPlayer import chat.simplex.res.MR import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt index 046571e008..465603d409 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import InfoRow import SectionBottomSpacer @@ -15,11 +15,10 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.PreferenceToggle +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.PreferenceToggle +import chat.simplex.common.model.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt index a92db078c3..9a4d4c3775 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -10,12 +10,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.* +import chat.simplex.common.model.* import chat.simplex.res.MR import kotlinx.datetime.Clock diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt similarity index 76% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt index a8f88d7ed8..91fb4a6e8b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/ScanCodeView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt @@ -1,15 +1,18 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import androidx.compose.foundation.layout.* import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCodeScanner +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCodeScanner import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource +@Composable +expect fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) + @Composable fun ScanCodeLayout(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { Column( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index e644cfde1c..df76617a23 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/SendMsgView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -1,7 +1,7 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat -import android.content.res.Configuration import androidx.compose.animation.core.* +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* @@ -17,18 +17,16 @@ import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.* -import androidx.compose.ui.window.Dialog -import chat.simplex.app.model.* -import chat.simplex.app.platform.LockToCurrentOrientationUntilDispose -import chat.simplex.app.platform.NativeKeyboard -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.ItemAction -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.ItemAction +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.ChatItem +import chat.simplex.common.platform.* import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import dev.icerock.moko.resources.compose.painterResource import kotlinx.coroutines.* @Composable @@ -68,7 +66,7 @@ fun SendMsgView( val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing && cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started) val showDeleteTextButton = rememberSaveable { mutableStateOf(false) } - NativeKeyboard(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange) + PlatformTextField(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange) // Disable clicks on text field if (cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) { Box( @@ -110,7 +108,7 @@ fun SendMsgView( } } } - !allowedToRecordVoiceByPlatform() -> + !allowedToRecordVoiceByPlatform() -> VoiceButtonWithoutPermissionByPlatform() else -> RecordVoiceView(recState, stopRecOnNextClick) @@ -195,6 +193,12 @@ fun SendMsgView( } } +@Composable +expect fun allowedToRecordVoiceByPlatform(): Boolean + +@Composable +expect fun VoiceButtonWithoutPermissionByPlatform() + @Composable private fun CustomDisappearingMessageDialog( sendMessage: (Int?) -> Unit, @@ -233,7 +237,7 @@ private fun CustomDisappearingMessageDialog( } } - Dialog(onDismissRequest = { setShowDialog(false) }) { + DefaultDialog(onDismissRequest = { setShowDialog(false) }) { Surface( shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) { @@ -265,7 +269,6 @@ private fun CustomDisappearingMessageDialog( .clickable { setShowDialog(false) } ) } - ChoiceButton(generalGetString(MR.strings.send_disappearing_message_30_seconds)) { sendMessage(30) setShowDialog(false) @@ -300,7 +303,7 @@ private fun BoxScope.DeleteTextButton(composeState: MutableState) @Composable private fun RecordVoiceView(recState: MutableState, stopRecOnNextClick: MutableState) { - val rec: Recorder = remember { RecorderNative() } + val rec: RecorderInterface = remember { RecorderNative() } DisposableEffect(Unit) { onDispose { rec.stop() } } val stopRecordingAndAddAudio: () -> Unit = { recState.value.filePathNullable?.let { @@ -560,12 +563,11 @@ private fun showDisabledVoiceAlert(isDirectChat: Boolean) { ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewSendMsgView() { val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) @@ -590,12 +592,11 @@ fun PreviewSendMsgView() { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewSendMsgViewEditing() { val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) @@ -621,12 +622,11 @@ fun PreviewSendMsgViewEditing() { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewSendMsgViewInProgress() { val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt similarity index 90% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index 390b839937..58e88ec12e 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat +package chat.simplex.common.views.chat import SectionBottomSpacer import SectionView @@ -10,15 +10,16 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCode +import chat.simplex.common.platform.shareText +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCode import chat.simplex.res.MR @Composable @@ -87,7 +88,8 @@ private fun VerifyCodeLayout( ) } Box(Modifier.weight(1f)) { - IconButton({ shareText(connectionCode) }, Modifier.size(20.dp).align(Alignment.CenterStart)) { + val clipboard = LocalClipboardManager.current + IconButton({ clipboard.shareText(connectionCode) }, Modifier.size(20.dp).align(Alignment.CenterStart)) { Icon(painterResource(MR.images.ic_share_filled), null, tint = MaterialTheme.colors.primary) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index 8a60681031..afe26f8f85 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.group +package chat.simplex.common.views.chat.group import SectionBottomSpacer import SectionCustomFooter @@ -6,7 +6,6 @@ import SectionDividerSpaced import SectionItemView import SectionSpacer import SectionView -import androidx.activity.compose.BackHandler import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -16,21 +15,22 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalView import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.platform.hideKeyboard -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.ChatInfoToolbarTitle -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.InfoAboutIncognito -import chat.simplex.app.views.usersettings.SettingsActionItem +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.ChatInfoToolbarTitle +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.InfoAboutIncognito +import chat.simplex.common.views.usersettings.SettingsActionItem +import chat.simplex.common.model.* +import chat.simplex.common.model.GroupInfo +import chat.simplex.common.platform.* import chat.simplex.res.MR @Composable @@ -185,7 +185,7 @@ private fun SearchRowView( SearchTextField(Modifier.fillMaxWidth(), searchText = searchText, alwaysVisible = true) { searchText.value = searchText.value.copy(it) } - val view = LocalView.current + val view = LocalMultiplatformView() LaunchedEffect(selectedContactsSize) { searchText.value = searchText.value.copy("") hideKeyboard(view) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index a2c8d21684..d93e69728f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.group +package chat.simplex.common.views.chat.group import InfoRow import SectionBottomSpacer @@ -7,8 +7,7 @@ import SectionItemView import SectionSpacer import SectionTextFooter import SectionView -import android.util.Log -import androidx.activity.compose.BackHandler +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -22,18 +21,18 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.* -import chat.simplex.app.views.chatlist.cantInviteIncognitoAlert -import chat.simplex.app.views.chatlist.setGroupMembers -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chatlist.cantInviteIncognitoAlert +import chat.simplex.common.views.chatlist.setGroupMembers +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.* +import chat.simplex.common.model.GroupInfo +import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.ClearChatButton +import chat.simplex.common.views.chat.clearChatDialog import chat.simplex.res.MR @Composable @@ -121,7 +120,7 @@ fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatM if (r) { chatModel.removeChat(chatInfo.id) chatModel.chatId.value = null - chatModel.controller.ntfManager.cancelNotificationsForChat(chatInfo.id) + ntfManager.cancelNotificationsForChat(chatInfo.id) close?.invoke() } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index ce1efdeafe..22b9c8a93a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.group +package chat.simplex.common.views.chat.group import SectionBottomSpacer import androidx.compose.foundation.layout.* @@ -10,15 +10,16 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalClipboardManager import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCode +import chat.simplex.common.model.* +import chat.simplex.common.platform.shareText +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCode import chat.simplex.res.MR @Composable @@ -43,13 +44,14 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St createLink() } } + val clipboard = LocalClipboardManager.current GroupLinkLayout( groupLink = groupLink, groupInfo, groupLinkMemberRole, creatingLink, createLink = ::createLink, - share = { shareText(groupLink ?: return@GroupLinkLayout) }, + share = { clipboard.shareText(groupLink ?: return@GroupLinkLayout) }, updateLink = { val role = groupLinkMemberRole.value if (role != null) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index c1bc15dbd9..53d98bfa1d 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.group +package chat.simplex.common.views.chat.group import InfoRow import SectionBottomSpacer @@ -6,9 +6,8 @@ import SectionDividerSpaced import SectionSpacer import SectionTextFooter import SectionView -import android.net.Uri -import android.util.Log -import androidx.activity.compose.BackHandler +import androidx.compose.desktop.ui.tooling.preview.Preview +import java.net.URI import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -16,21 +15,20 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalClipboardManager import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.* -import chat.simplex.app.views.usersettings.SettingsActionItem +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.* +import chat.simplex.common.views.usersettings.SettingsActionItem +import chat.simplex.common.model.GroupInfo +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.datetime.Clock @@ -73,7 +71,7 @@ fun GroupMemberInfoView( } }, connectViaAddress = { connReqUri -> - val uri = Uri.parse(connReqUri) + val uri = URI(connReqUri) withUriAction(uri) { linkType -> withApi { Log.d(TAG, "connectViaUri: connecting") @@ -218,10 +216,10 @@ fun GroupMemberInfoLayout( } if (member.contactLink != null) { - val context = LocalContext.current SectionView(stringResource(MR.strings.address_section_title).uppercase()) { QRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) - ShareAddressButton { shareText(member.contactLink) } + val clipboard = LocalClipboardManager.current + ShareAddressButton { clipboard.shareText(member.contactLink) } if (contactId != null) { if (knownDirectChat(contactId) == null && !groupInfo.fullGroupPreferences.directMessages.on) { ConnectViaAddressButton(onClick = { connectViaAddress(member.contactLink) }) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 1801a0ff75..d95d2a4cff 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.group +package chat.simplex.common.views.chat.group import InfoRow import SectionBottomSpacer @@ -14,11 +14,10 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.PreferenceToggleWithIcon +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon +import chat.simplex.common.model.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index 1be6cca41e..ac13dd483a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -1,8 +1,7 @@ -package chat.simplex.app.views.chat.group +package chat.simplex.common.views.chat.group import SectionBottomSpacer -import android.content.res.Configuration -import android.net.Uri +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -15,24 +14,20 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.platform.cropToSquare -import chat.simplex.app.platform.resizeImageToStrSize -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.ProfileNameField -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.isValidDisplayName -import chat.simplex.app.views.onboarding.ReadableText -import chat.simplex.app.views.usersettings.* -import com.google.accompanist.insets.ProvideWindowInsets -import com.google.accompanist.insets.navigationBarsWithImePadding +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.ProfileNameField +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.isValidDisplayName +import chat.simplex.common.views.onboarding.ReadableText +import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.net.URI @Composable fun GroupProfileView(groupInfo: GroupInfo, chatModel: ChatModel, close: () -> Unit) { @@ -60,7 +55,7 @@ fun GroupProfileLayout( val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = rememberSaveable { mutableStateOf(groupProfile.displayName) } val fullName = rememberSaveable { mutableStateOf(groupProfile.fullName) } - val chosenImage = rememberSaveable { mutableStateOf(null) } + val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(groupProfile.image) } val scope = rememberCoroutineScope() val scrollState = rememberScrollState() @@ -193,12 +188,11 @@ private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewGroupProfileLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index 2e04171ea2..3be54376d5 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.group +package chat.simplex.common.views.chat.group import SectionBottomSpacer import SectionDividerSpaced @@ -14,15 +14,18 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.platform.copyText -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.chat.item.MarkdownText -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.chat.item.MarkdownText +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.GroupInfo import chat.simplex.res.MR import kotlinx.coroutines.delay @@ -98,15 +101,17 @@ private fun GroupWelcomeLayout( }, wt.value.isEmpty() ) - CopyTextButton { copyText(wt.value) } + val clipboard = LocalClipboardManager.current + CopyTextButton { clipboard.setText(AnnotatedString(wt.value)) } SectionDividerSpaced(maxBottomPadding = false) SaveButton( save = save, disabled = wt.value == groupInfo.groupProfile.description || (wt.value == "" && groupInfo.groupProfile.description == null) ) } else { + val clipboard = LocalClipboardManager.current TextPreview(wt.value, linkMode) - CopyTextButton { copyText(wt.value) } + CopyTextButton { clipboard.setText(AnnotatedString(wt.value)) } } SectionBottomSpacer() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CICallItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CICallItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt index b2f2245d23..3e4dce7f4a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CICallItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -10,9 +10,9 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.model.* +import chat.simplex.common.views.helpers.SimpleButton import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIChatFeatureView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt similarity index 88% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIChatFeatureView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt index 752cf8681b..e919ab1aa8 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIChatFeatureView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -9,7 +9,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* +import chat.simplex.common.model.ChatItem +import chat.simplex.common.model.Feature @Composable fun CIChatFeatureView( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIEventView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIEventView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt index 32933a57d7..09d0bd4d04 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIEventView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt @@ -1,6 +1,6 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.content.res.Configuration +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material.* @@ -9,11 +9,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.ChatItem -import chat.simplex.app.ui.theme.* +import chat.simplex.common.model.ChatItem +import chat.simplex.common.ui.theme.* @Composable fun CIEventView(ci: ChatItem) { @@ -46,11 +45,10 @@ fun chatEventText(ci: ChatItem): AnnotatedString = withStyle(chatEventStyle) { append(ci.content.text + " " + ci.timestampText) } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode" -) +)*/ @Composable fun CIEventViewPreview() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFeaturePreferenceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFeaturePreferenceView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFeaturePreferenceView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFeaturePreferenceView.kt index cdb261d58b..1a23dfc49a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFeaturePreferenceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFeaturePreferenceView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -9,9 +9,8 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.model.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt similarity index 83% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index 21e6346eca..6207c1648e 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -1,10 +1,5 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.net.Uri -import android.widget.Toast -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CornerSize @@ -16,22 +11,18 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.* -import chat.simplex.app.platform.getLoadedFilePath -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR -import kotlinx.datetime.Clock -import java.io.BufferedOutputStream import java.io.File +import java.net.URI @Composable fun CIFileView( @@ -39,7 +30,6 @@ fun CIFileView( edited: Boolean, receiveFile: (Long) -> Unit ) { - val context = LocalContext.current val saveFileLauncher = rememberSaveFileLauncher(ciFile = file) @Composable @@ -105,9 +95,11 @@ fun CIFileView( is CIFileStatus.RcvComplete -> { val filePath = getLoadedFilePath(file) if (filePath != null) { - saveFileLauncher.launch(file.fileName) + withApi { + saveFileLauncher.launch(file.fileName) + } } else { - Toast.makeText(context, generalGetString(MR.strings.file_not_found), Toast.LENGTH_SHORT).show() + showToast(generalGetString(MR.strings.file_not_found)) } } else -> {} @@ -214,29 +206,15 @@ fun CIFileView( } @Composable -fun rememberSaveFileLauncher(ciFile: CIFile?): ManagedActivityResultLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument(), - onResult = { destination -> - destination?.let { - val cxt = SimplexApp.context - val filePath = getLoadedFilePath(ciFile) - if (filePath != null) { - val contentResolver = cxt.contentResolver - contentResolver.openOutputStream(destination)?.let { stream -> - val outputStream = BufferedOutputStream(stream) - File(filePath).inputStream().use { it.copyTo(outputStream) } - outputStream.close() - Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(cxt, generalGetString(MR.strings.file_not_found), Toast.LENGTH_SHORT).show() - } - } +fun rememberSaveFileLauncher(ciFile: CIFile?): FileChooserLauncher = + rememberFileChooserLauncher(false) { to: URI? -> + val filePath = getLoadedFilePath(ciFile) + if (filePath != null && to != null) { + copyFileToFile(File(filePath), to) {} } - ) - + } +/* class ChatItemProvider: PreviewParameterProvider { private val sentFile = ChatItem( chatDir = CIDirection.DirectSnd(), @@ -275,4 +253,4 @@ fun PreviewCIFileFramedItemView(@PreviewParameter(ChatItemProvider::class) chatI SimpleXTheme { FramedItemView(ChatInfo.Direct.sampleData, chatItem, linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {}) } -} +}*/ diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIGroupInvitationView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIGroupInvitationView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt index d66dc4ae23..6ee29b8304 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIGroupInvitationView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt @@ -1,6 +1,6 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.content.res.Configuration +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -11,13 +11,11 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* import chat.simplex.res.MR @Composable @@ -115,11 +113,10 @@ fun CIGroupInvitationView( } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode" -) +)*/ @Composable fun PendingCIGroupInvitationViewPreview() { SimpleXTheme { @@ -132,11 +129,10 @@ fun PendingCIGroupInvitationViewPreview() { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode" -) +)*/ @Composable fun CIGroupInvitationViewAcceptedPreview() { SimpleXTheme { @@ -149,7 +145,7 @@ fun CIGroupInvitationViewAcceptedPreview() { } } -@Preview(showBackground = true) +@Preview @Composable fun CIGroupInvitationViewLongNamePreview() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt similarity index 84% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index 35bc7ad55c..db772d9dc6 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIImageView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.graphics.Bitmap import androidx.compose.foundation.Image import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* @@ -9,25 +8,22 @@ import androidx.compose.material.Icon import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.* -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.core.content.FileProvider -import chat.simplex.app.BuildConfig -import chat.simplex.app.model.* -import chat.simplex.app.platform.base64ToBitmap -import chat.simplex.app.platform.getLoadedFilePath -import chat.simplex.app.views.helpers.* -import chat.simplex.res.MR -import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource import java.io.File +import java.net.URI @Composable fun CIImageView( @@ -89,13 +85,13 @@ fun CIImageView( @Composable fun imageViewFullWidth(): Dp { val approximatePadding = 100.dp - return with(LocalDensity.current) { minOf(1000.dp, LocalView.current.width.toDp() - approximatePadding) } + return with(LocalDensity.current) { minOf(1000.dp, LocalWindowWidth() - approximatePadding) } } @Composable - fun imageView(imageBitmap: Bitmap, onClick: () -> Unit) { + fun imageView(imageBitmap: ImageBitmap, onClick: () -> Unit) { Image( - imageBitmap.asImageBitmap(), + imageBitmap, contentDescription = stringResource(MR.strings.image_descr), // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView // if text is short and take all available width if text is long @@ -110,7 +106,7 @@ fun CIImageView( } @Composable - fun imageView(painter: Painter, onClick: () -> Unit) { + fun ImageView(painter: Painter, onClick: () -> Unit) { Image( painter, contentDescription = stringResource(MR.strings.image_descr), @@ -133,8 +129,8 @@ fun CIImageView( return false } - fun imageAndFilePath(file: CIFile?): Pair { - val imageBitmap: Bitmap? = getLoadedImage(file) + fun imageAndFilePath(file: CIFile?): Pair { + val imageBitmap: ImageBitmap? = getLoadedImage(file) val filePath = getLoadedFilePath(file) return imageBitmap to filePath } @@ -143,11 +139,10 @@ fun CIImageView( Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), contentAlignment = Alignment.TopEnd ) { - val context = LocalContext.current val (imageBitmap, filePath) = remember(file) { imageAndFilePath(file) } if (imageBitmap != null && filePath != null) { - val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) } - SimpleAndAnimatedImageView(uri, imageBitmap.asImageBitmap(), file, imageProvider, @Composable { painter, onClick -> imageView(painter, onClick) }) + val uri = remember(filePath) { getAppFileUri(filePath.substringAfterLast(File.separator)) } + SimpleAndAnimatedImageView(uri, imageBitmap, file, imageProvider, @Composable { painter, onClick -> ImageView(painter, onClick) }) } else { imageView(base64ToBitmap(image), onClick = { if (file != null) { @@ -186,3 +181,11 @@ fun CIImageView( } } +@Composable +expect fun SimpleAndAnimatedImageView( + uri: URI, + imageBitmap: ImageBitmap, + file: CIFile?, + imageProvider: () -> ImageGalleryProvider, + ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit +) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIInvalidJSONView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt similarity index 74% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIInvalidJSONView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt index dc0ef7f415..92baa6a971 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIInvalidJSONView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import SectionView import androidx.compose.foundation.* @@ -7,14 +7,15 @@ import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalClipboardManager import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.SettingsActionItem +import chat.simplex.common.platform.shareText +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR @Composable @@ -32,8 +33,9 @@ fun InvalidJSONView(json: String) { Column { Spacer(Modifier.height(DEFAULT_PADDING)) SectionView { + val clipboard = LocalClipboardManager.current SettingsActionItem(painterResource(MR.images.ic_share), generalGetString(MR.strings.share_verb), click = { - shareText(json) + clipboard.shareText(json) }) } Column(Modifier.padding(DEFAULT_PADDING).fillMaxWidth().verticalScroll(rememberScrollState())) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt similarity index 94% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt index 9404b24369..dd7d2ca557 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -9,11 +9,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.CurrentColors +import chat.simplex.common.ui.theme.CurrentColors +import chat.simplex.common.model.* import chat.simplex.res.MR import kotlinx.datetime.Clock @@ -159,7 +159,7 @@ fun PreviewCIMetaViewEditedUnread() { chatItem = ChatItem.getSampleData( 1, CIDirection.DirectRcv(), Clock.System.now(), "hello", itemEdited = true, - status=CIStatus.RcvNew() + status= CIStatus.RcvNew() ), null ) @@ -172,7 +172,7 @@ fun PreviewCIMetaViewEditedSent() { chatItem = ChatItem.getSampleData( 1, CIDirection.DirectSnd(), Clock.System.now(), "hello", itemEdited = true, - status=CIStatus.SndSent() + status= CIStatus.SndSent() ), null ) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIRcvDecryptionError.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt similarity index 81% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIRcvDecryptionError.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt index 80e4ea15ba..e0aa04afbb 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIRcvDecryptionError.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt @@ -1,10 +1,10 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.runtime.Composable -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.views.helpers.AlertManager -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.views.helpers.AlertManager +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.model.ChatItem +import chat.simplex.common.model.MsgDecryptError import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt index 6a421c6348..650c525695 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVIdeoView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt @@ -1,7 +1,5 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.graphics.Bitmap -import android.net.Uri import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CornerSize @@ -9,27 +7,22 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.* +import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.layout.* import androidx.compose.ui.platform.* -import androidx.compose.ui.unit.* -import androidx.core.content.FileProvider -import chat.simplex.app.BuildConfig -import chat.simplex.app.model.* -import chat.simplex.app.platform.* -import chat.simplex.app.ui.theme.DEFAULT_PADDING_HALF -import chat.simplex.app.ui.theme.WarningOrange -import chat.simplex.app.views.helpers.* -import chat.simplex.res.MR -import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.* +import chat.simplex.res.MR +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import dev.icerock.moko.resources.StringResource import java.io.File +import java.net.URI @Composable fun CIVideoView( @@ -44,12 +37,11 @@ fun CIVideoView( Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), contentAlignment = Alignment.TopEnd ) { - val context = LocalContext.current val filePath = remember(file) { getLoadedFilePath(file) } val preview = remember(image) { base64ToBitmap(image) } if (file != null && filePath != null) { - val uri = remember(filePath) { FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(filePath)) } - val view = LocalView.current + val uri = remember(filePath) { getAppFileUri(filePath.substringAfterLast(File.separator)) } + val view = LocalMultiplatformView() VideoView(uri, file, preview, duration * 1000L, showMenu, onClick = { hideKeyboard(view) ModalManager.shared.showCustomModal(animated = false) { close -> @@ -97,8 +89,7 @@ fun CIVideoView( } @Composable -private fun VideoView(uri: Uri, file: CIFile, defaultPreview: Bitmap, defaultDuration: Long, showMenu: MutableState, onClick: () -> Unit) { - val context = LocalContext.current +private fun VideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defaultDuration: Long, showMenu: MutableState, onClick: () -> Unit) { val player = remember(uri) { VideoPlayer.getOrCreate(uri, false, defaultPreview, defaultDuration, true) } val videoPlaying = remember(uri.path) { player.videoPlaying } val progress = remember(uri.path) { player.progress } @@ -138,6 +129,9 @@ private fun VideoView(uri: Uri, file: CIFile, defaultPreview: Bitmap, defaultDur } } +@Composable +expect fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: () -> Unit, stop: () -> Unit) + @Composable private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, onClick: () -> Unit) { Surface( @@ -206,11 +200,11 @@ private fun DurationProgress(file: CIFile, playing: MutableState, durat } @Composable -private fun ImageView(preview: Bitmap, showMenu: MutableState, onClick: () -> Unit) { +private fun ImageView(preview: ImageBitmap, showMenu: MutableState, onClick: () -> Unit) { val windowWidth = LocalWindowWidth() val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp } Image( - preview.asImageBitmap(), + preview, contentDescription = stringResource(MR.strings.video_descr), modifier = Modifier .width(width) @@ -222,6 +216,9 @@ private fun ImageView(preview: Bitmap, showMenu: MutableState, onClick: ) } +@Composable +expect fun LocalWindowWidth(): Dp + @Composable private fun progressIndicator() { CircularProgressIndicator( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt index de1cd602d3..cb557b0516 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* @@ -15,13 +15,14 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.* -import androidx.compose.ui.unit.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.getLoadedFilePath -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource +import androidx.compose.ui.unit.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.getLoadedFilePath +import chat.simplex.common.platform.AudioPlayer +import chat.simplex.res.MR import kotlinx.coroutines.flow.distinctUntilChanged // TODO refactor https://github.com/simplex-chat/simplex-chat/pull/1451#discussion_r1033429901 @@ -43,7 +44,6 @@ fun CIVoiceView( verticalAlignment = Alignment.CenterVertically ) { if (file != null) { - val context = LocalContext.current val filePath = remember(file.filePath, file.fileStatus) { getLoadedFilePath(file) } var brokenAudio by rememberSaveable(file.filePath) { mutableStateOf(false) } val audioPlaying = rememberSaveable(file.filePath) { mutableStateOf(false) } @@ -108,7 +108,7 @@ private fun VoiceLayout( MaterialTheme.colors.primary.mixWith( backgroundColor.copy(1f).mixWith(MaterialTheme.colors.background, backgroundColor.alpha), 0.24f) - val width = with(LocalDensity.current) { LocalView.current.width.toDp() } + val width = LocalWindowWidth() val colors = SliderDefaults.colors( inactiveTrackColor = inactiveTrackColor ) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt similarity index 94% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 2e2c1676e5..2fe7d94f6d 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -1,5 +1,6 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -12,17 +13,18 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.* +import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.platform.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.chat.ComposeContextItem +import chat.simplex.common.views.chat.ComposeState import chat.simplex.res.MR import kotlinx.datetime.Clock @@ -154,6 +156,7 @@ fun ChatItemView( @Composable fun MsgContentItemDropdownMenu() { + val saveFileLauncher = rememberSaveFileLauncher(ciFile = cItem.file) DefaultDropdownMenu(showMenu) { if (cInfo.featureEnabled(ChatFeature.Reactions) && cItem.allowAddReaction) { MsgReactionsMenu() @@ -168,23 +171,21 @@ fun ChatItemView( showMenu.value = false }) } + val clipboard = LocalClipboardManager.current ItemAction(stringResource(MR.strings.share_verb), painterResource(MR.images.ic_share), onClick = { val filePath = getLoadedFilePath(cItem.file) when { filePath != null -> shareFile(cItem.text, filePath) - else -> shareText(cItem.content.text) + else -> clipboard.shareText(cItem.content.text) } showMenu.value = false }) ItemAction(stringResource(MR.strings.copy_verb), painterResource(MR.images.ic_content_copy), onClick = { - copyText(cItem.content.text) + clipboard.setText(AnnotatedString(cItem.content.text)) showMenu.value = false }) - if (cItem.content.msgContent is MsgContent.MCImage || cItem.content.msgContent is MsgContent.MCVideo || cItem.content.msgContent is MsgContent.MCFile || cItem.content.msgContent is MsgContent.MCVoice) { - val filePath = getLoadedFilePath(cItem.file) - if (filePath != null) { - SaveContentItemAction(cItem, showMenu) - } + if (cItem.content.msgContent is MsgContent.MCImage || cItem.content.msgContent is MsgContent.MCVideo || cItem.content.msgContent is MsgContent.MCFile || cItem.content.msgContent is MsgContent.MCVoice && getLoadedFilePath(cItem.file) != null) { + SaveContentItemAction(cItem, saveFileLauncher, showMenu) } if (cItem.meta.editable && cItem.content.msgContent !is MsgContent.MCVoice && !live) { ItemAction(stringResource(MR.strings.edit_verb), painterResource(MR.images.ic_edit_filled), onClick = { @@ -202,8 +203,9 @@ fun ChatItemView( } ) } - if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null) { - CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction) + val cancelAction = cItem.file?.cancelAction + if (cItem.meta.itemDeleted == null && cItem.file != null && cancelAction != null) { + CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cancelAction) } ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) if (!(live && cItem.meta.isLive)) { @@ -314,6 +316,9 @@ fun ChatItemView( } } +@Composable +expect fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserLauncher, showMenu: MutableState) + @Composable fun CancelFileItemAction( fileId: Long, diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/DeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt similarity index 86% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/DeletedItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt index 49a9ce28e6..3d8d9f7940 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/DeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -10,11 +9,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.ChatItem -import chat.simplex.app.ui.theme.* +import chat.simplex.common.model.ChatItem +import chat.simplex.common.ui.theme.* @Composable fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) { @@ -42,11 +41,10 @@ fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode" -) +)*/ @Composable fun PreviewDeletedItemView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/EmojiItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/EmojiItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt index 86fffc6ae0..d99de4019d 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/EmojiItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.ChatItem +import chat.simplex.common.model.ChatItem val largeEmojiFont: TextStyle = TextStyle(fontSize = 48.sp) val mediumEmojiFont: TextStyle = TextStyle(fontSize = 36.sp) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index f85db89106..18a14efe34 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -19,13 +19,12 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.* import androidx.compose.ui.unit.* -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.platform.base64ToBitmap -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.base64ToBitmap import chat.simplex.res.MR import kotlinx.datetime.Clock import kotlin.math.min @@ -117,7 +116,7 @@ fun FramedItemView( Box(Modifier.fillMaxWidth().weight(1f)) { ciQuotedMsgView(qi) } - val imageBitmap = base64ToBitmap(qi.content.image).asImageBitmap() + val imageBitmap = base64ToBitmap(qi.content.image) Image( imageBitmap, contentDescription = stringResource(MR.strings.image_descr), @@ -129,7 +128,7 @@ fun FramedItemView( Box(Modifier.fillMaxWidth().weight(1f)) { ciQuotedMsgView(qi) } - val imageBitmap = base64ToBitmap(qi.content.image).asImageBitmap() + val imageBitmap = base64ToBitmap(qi.content.image) Image( imageBitmap, contentDescription = stringResource(MR.strings.video_descr), @@ -284,8 +283,8 @@ fun PriorityLayout( content: @Composable () -> Unit ) { /** - * Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints] - * */ + * Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints] + * */ fun maxSafeHeight(width: Int) = when { // width bits + height bits should be <= 31 width < 0x1FFF /*MaxNonFocusMask*/ -> 0x3FFFF - 1 /* MaxFocusMask */ // 13 bits width + 18 bits height width < 0x7FFF /*MinNonFocusMask*/ -> 0xFFFF - 1 /* MinFocusMask */ // 15 bits width + 16 bits height @@ -318,6 +317,7 @@ fun PriorityLayout( } } } +/* class EditedProvider: PreviewParameterProvider { override val values = listOf(false, true).asSequence() @@ -507,3 +507,4 @@ fun PreviewQuoteWithLongTextAndFile(@PreviewParameter(EditedProvider::class) edi ) } } +*/ diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt similarity index 79% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt index d39baa29e2..270c671fc1 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt @@ -1,41 +1,23 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.graphics.Bitmap -import android.net.Uri -import android.os.Build -import android.view.View -import androidx.activity.compose.BackHandler import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* -import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.input.pointer.* -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.* -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.view.isVisible -import chat.simplex.app.platform.base64ToBitmap -import chat.simplex.app.views.chat.ProviderMedia -import chat.simplex.app.views.helpers.* -import coil.ImageLoader -import coil.compose.rememberAsyncImagePainter -import coil.decode.GifDecoder -import coil.decode.ImageDecoderDecoder -import coil.request.ImageRequest -import coil.size.Size -import com.google.accompanist.pager.* -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout -import com.google.android.exoplayer2.ui.StyledPlayerView -import chat.simplex.res.MR +import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.ProviderMedia +import chat.simplex.common.views.helpers.* import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch +import java.net.URI import kotlin.math.absoluteValue interface ImageGalleryProvider { @@ -47,7 +29,6 @@ interface ImageGalleryProvider { fun onDismiss(index: Int) } -@OptIn(ExperimentalPagerApi::class) @Composable fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> Unit) { val provider = remember { imageProvider() } @@ -63,11 +44,11 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> } } val scope = rememberCoroutineScope() - val playersToRelease = rememberSaveable { mutableSetOf() } + val playersToRelease = rememberSaveable { mutableSetOf() } DisposableEffectOnGone( whenGone = { playersToRelease.forEach { VideoPlayer.release(it, true, true) } } ) - HorizontalPager(count = remember { provider.totalMediaSize }.value, state = pagerState) { index -> + HorizontalPager(pageCount = remember { provider.totalMediaSize }.value, state = pagerState) { index -> Column( Modifier .fillMaxSize() @@ -139,8 +120,10 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> ) } .fillMaxSize() + // LALAL + // https://github.com/JetBrains/compose-multiplatform/pull/2015/files#diff-841b3825c504584012e1d1c834d731bae794cce6acad425d81847c8bbbf239e0R24 if (media is ProviderMedia.Image) { - val (uri: Uri, imageBitmap: Bitmap) = media + val (uri: URI, imageBitmap: ImageBitmap) = media FullScreenImageView(modifier, uri, imageBitmap) } else if (media is ProviderMedia.Video) { val preview = remember(media.uri.path) { base64ToBitmap(media.preview) } @@ -155,8 +138,10 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> } @Composable -private fun VideoView(modifier: Modifier, uri: Uri, defaultPreview: Bitmap, currentPage: Boolean) { - val context = LocalContext.current +expect fun FullScreenImageView(modifier: Modifier, uri: URI, imageBitmap: ImageBitmap) + +@Composable +private fun VideoView(modifier: Modifier, uri: URI, defaultPreview: ImageBitmap, currentPage: Boolean) { val player = remember(uri) { VideoPlayer.getOrCreate(uri, true, defaultPreview, 0L, true) } val isCurrentPage = rememberUpdatedState(currentPage) val play = { @@ -176,3 +161,6 @@ private fun VideoView(modifier: Modifier, uri: Uri, defaultPreview: Bitmap, curr FullScreenVideoView(player, modifier) } } + +@Composable +expect fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/IntegrityErrorItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt similarity index 86% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/IntegrityErrorItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt index 77a0219ca4..7cd996ec25 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/IntegrityErrorItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -13,16 +12,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.ChatItem -import chat.simplex.app.model.MsgErrorType -import chat.simplex.app.ui.theme.CurrentColors -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.AlertManager -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.model.ChatItem +import chat.simplex.common.model.MsgErrorType +import chat.simplex.common.ui.theme.CurrentColors +import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.views.helpers.AlertManager +import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR @Composable @@ -76,11 +74,10 @@ fun CIMsgError(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode" -) +)*/ @Composable fun IntegrityErrorItemViewPreview() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/MarkedDeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/MarkedDeletedItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt index 22192a3692..8ced77289c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/MarkedDeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt @@ -1,6 +1,4 @@ -package chat.simplex.app.views.chat.item - -import android.content.res.Configuration +package chat.simplex.common.views.chat.item import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -11,14 +9,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.CIDeleted -import chat.simplex.app.model.ChatItem -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.model.CIDeleted +import chat.simplex.common.model.ChatItem +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR import kotlinx.datetime.Clock @@ -60,11 +57,10 @@ private fun MarkedDeletedText(text: String) { ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode" -) +)*/ @Composable fun PreviewMarkedDeletedItemView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt similarity index 87% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt index de5fd1594e..07c5db4d71 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt @@ -1,9 +1,5 @@ -package chat.simplex.app.views.chat.item +package chat.simplex.common.views.chat.item -import android.app.Activity -import android.content.ActivityNotFoundException -import android.util.Log -import androidx.annotation.IntRange import androidx.compose.foundation.text.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -19,11 +15,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* -import androidx.core.text.BidiFormatter -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.CurrentColors -import chat.simplex.app.views.helpers.detectGesture +import chat.simplex.common.model.* +import chat.simplex.common.platform.Log +import chat.simplex.common.platform.TAG +import chat.simplex.common.ui.theme.CurrentColors +import chat.simplex.common.views.helpers.DisposableEffectOnGone +import chat.simplex.common.views.helpers.detectGesture import kotlinx.coroutines.* val reserveTimestampStyle = SpanStyle(color = Color.Transparent) @@ -56,7 +53,7 @@ private val typingIndicators: List = listOf( ) -private fun typingIndicator(recent: Boolean, @IntRange (from = 0, to = 4) typingIdx: Int): AnnotatedString = buildAnnotatedString { +private fun typingIndicator(recent: Boolean, typingIdx: Int): AnnotatedString = buildAnnotatedString { pushStyle(SpanStyle(color = CurrentColors.value.colors.secondary, fontFamily = FontFamily.Monospace, letterSpacing = (-1).sp)) append(if (recent) typingIndicators[typingIdx] else noTyping) } @@ -82,7 +79,7 @@ fun MarkdownText ( onLinkLongClick: (link: String) -> Unit = {} ) { val textLayoutDirection = remember (text) { - if (BidiFormatter.getInstance().isRtl(text.subSequence(0, kotlin.math.min(50, text.length)))) LayoutDirection.Rtl else LayoutDirection.Ltr + if (isRtl(text.subSequence(0, kotlin.math.min(50, text.length)))) LayoutDirection.Rtl else LayoutDirection.Ltr } val reserve = if (textLayoutDirection != LocalLayoutDirection.current && meta != null) { "\n" @@ -117,18 +114,14 @@ fun MarkdownText ( } } if (meta?.isLive == true) { - val activity = LocalContext.current as Activity LaunchedEffect(meta.recent, meta.isLive) { switchTyping() } - DisposableEffect(Unit) { - val orientation = activity.resources.configuration.orientation - onDispose { - if (orientation == activity.resources.configuration.orientation) { - stopTyping() - } + DisposableEffectOnGone( + whenGone = { + stopTyping() } - } + ) } if (formattedText == null) { val annotatedText = buildAnnotatedString { @@ -183,7 +176,7 @@ fun MarkdownText ( .firstOrNull()?.let { annotation -> try { uriHandler.openUri(annotation.item) - } catch (e: ActivityNotFoundException) { + } catch (e: Exception) { // It can happen, for example, when you click on a text 0.00001 but don't have any app that can catch // `tel:` scheme in url installed on a device (no phone app or contacts, maybe) Log.e(TAG, "Open url: ${e.stackTraceToString()}") @@ -228,12 +221,12 @@ fun ClickableText( } } }, shouldConsumeEvent = { pos -> - var consume = false - layoutResult.value?.let { layoutResult -> - consume = shouldConsumeEvent(layoutResult.getOffsetForPosition(pos)) - } - consume + var consume = false + layoutResult.value?.let { layoutResult -> + consume = shouldConsumeEvent(layoutResult.getOffsetForPosition(pos)) } + consume + } ) } @@ -250,3 +243,13 @@ fun ClickableText( } ) } + +private fun isRtl(s: CharSequence): Boolean { + for (element in s) { + val d = Character.getDirectionality(element) + if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT || d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC || d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING || d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE) { + return true + } + } + return false +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt index 94f9632080..0505f58dc2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist -import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -11,15 +10,14 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.annotatedStringResource -import chat.simplex.app.views.onboarding.ReadableTextWithLink -import chat.simplex.app.views.usersettings.MarkdownHelpView -import chat.simplex.app.views.usersettings.simplexTeamUri +import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.views.helpers.annotatedStringResource +import chat.simplex.common.views.onboarding.ReadableTextWithLink +import chat.simplex.common.views.usersettings.MarkdownHelpView +import chat.simplex.common.views.usersettings.simplexTeamUri import chat.simplex.res.MR val bold = SpanStyle(fontWeight = FontWeight.Bold) @@ -76,12 +74,11 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewChatHelpLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 4f3294706b..41298b0521 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist -import android.content.res.Configuration import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -13,18 +12,20 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.* -import chat.simplex.app.views.chat.group.deleteGroupDialog -import chat.simplex.app.views.chat.group.leaveGroupDialog -import chat.simplex.app.views.chat.item.InvalidJSONView -import chat.simplex.app.views.chat.item.ItemAction -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.ContactConnectionInfoView +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.chat.group.deleteGroupDialog +import chat.simplex.common.views.chat.group.leaveGroupDialog +import chat.simplex.common.views.chat.item.InvalidJSONView +import chat.simplex.common.views.chat.item.ItemAction +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.ContactConnectionInfoView +import chat.simplex.common.model.* +import chat.simplex.common.platform.ntfManager import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.datetime.Clock @@ -194,7 +195,7 @@ fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState< painterResource(MR.images.ic_check), onClick = { markChatRead(chat, chatModel) - chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id) + ntfManager.cancelNotificationsForChat(chat.id) showMenu.value = false } ) @@ -537,7 +538,7 @@ fun deleteGroup(groupInfo: GroupInfo, chatModel: ChatModel) { if (r) { chatModel.removeChat(groupInfo.id) chatModel.chatId.value = null - chatModel.controller.ntfManager.cancelNotificationsForChat(groupInfo.id) + ntfManager.cancelNotificationsForChat(groupInfo.id) } } } @@ -582,7 +583,7 @@ fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatMo if (res && newChatInfo != null) { chatModel.updateChatInfo(newChatInfo) if (!chatSettings.enableNtfs) { - chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id) + ntfManager.cancelNotificationsForChat(chat.id) } val current = currentState?.value if (current != null) { @@ -618,12 +619,11 @@ fun ChatListNavLinkLayout( Divider(Modifier.padding(horizontal = 8.dp)) } -@Preview -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewChatListNavLinkDirect() { SimpleXTheme { @@ -659,12 +659,11 @@ fun PreviewChatListNavLinkDirect() { } } -@Preview -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewChatListNavLinkGroup() { SimpleXTheme { @@ -700,12 +699,11 @@ fun PreviewChatListNavLinkGroup() { } } -@Preview -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewChatListNavLinkContactRequest() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 116106853c..9c372020fc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -1,8 +1,5 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist -import android.net.Uri -import android.util.Log -import androidx.activity.compose.BackHandler import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* @@ -21,19 +18,20 @@ import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.unit.* -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.* -import chat.simplex.app.views.onboarding.WhatsNewView -import chat.simplex.app.views.onboarding.shouldShowWhatsNew -import chat.simplex.app.views.usersettings.SettingsView -import chat.simplex.app.views.usersettings.simplexTeamUri +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.WhatsNewView +import chat.simplex.common.views.onboarding.shouldShowWhatsNew +import chat.simplex.common.views.usersettings.SettingsView +import chat.simplex.common.views.usersettings.simplexTeamUri +import chat.simplex.common.platform.* +import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import java.net.URI @Composable fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { @@ -180,7 +178,7 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user if (chatModel.chats.size > 0) { barButtons.add { IconButton({ showSearch = true }) { - Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb).capitalize(Locale.current), tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary) } } } @@ -281,7 +279,7 @@ private fun BoxScope.unreadBadge(text: String? = "") { @Composable private fun ToggleFilterButton() { - val pref = remember { SimplexApp.context.chatModel.controller.appPrefs.showUnreadAndFavorites } + val pref = remember { ChatController.appPrefs.showUnreadAndFavorites } IconButton(onClick = { pref.set(!pref.get()) }) { Icon( painterResource(MR.images.ic_filter_list), @@ -308,7 +306,7 @@ private fun ProgressIndicator() { ) } -fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { +fun connectIfOpenedViaUri(uri: URI, chatModel: ChatModel) { Log.d(TAG, "connectIfOpenedViaUri: opened via link") if (chatModel.currentUser.value == null) { chatModel.appOpenUrl.value = uri @@ -345,7 +343,7 @@ private fun ChatList(chatModel: ChatModel, search: String) { DisposableEffect(Unit) { onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } } - val showUnreadAndFavorites = remember { chatModel.controller.appPrefs.showUnreadAndFavorites.state }.value + val showUnreadAndFavorites = remember { ChatController.appPrefs.showUnreadAndFavorites.state }.value val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search) } } LazyColumn( modifier = Modifier.fillMaxWidth(), @@ -363,7 +361,7 @@ private fun ChatList(chatModel: ChatModel, search: String) { } private fun filteredChats(showUnreadAndFavorites: Boolean, searchText: String): List { - val chatModel = SimplexApp.context.chatModel + val chatModel = ChatModel val s = searchText.trim().lowercase() return if (s.isEmpty() && !showUnreadAndFavorites) chatModel.chats @@ -395,4 +393,3 @@ private fun filtered(chat: Chat): Boolean = private fun viewNameContains(cInfo: ChatInfo, s: String): Boolean = cInfo.chatViewName.lowercase().contains(s.lowercase()) - diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index efa51a3790..04e0724c27 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist -import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape @@ -17,15 +16,15 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.* -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.ComposePreview -import chat.simplex.app.views.chat.ComposeState -import chat.simplex.app.views.chat.item.MarkdownText -import chat.simplex.app.views.helpers.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.ComposePreview +import chat.simplex.common.views.chat.ComposeState +import chat.simplex.common.views.chat.item.MarkdownText +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.model.GroupInfo import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource @@ -312,12 +311,11 @@ fun ChatStatusImage(s: NetworkStatus?) { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewChatPreviewView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactConnectionView.kt similarity index 87% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactConnectionView.kt index 39c0b583a3..7931034de3 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactConnectionView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme @@ -7,14 +7,14 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity -import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.ProfileImage +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.ProfileImage +import chat.simplex.common.model.PendingContactConnection +import chat.simplex.common.model.getTimestampText import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt similarity index 87% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt index 8d95226730..6ec03ad7f5 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme @@ -11,10 +11,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.ChatInfoImage +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.ChatInfoImage +import chat.simplex.common.model.ChatInfo +import chat.simplex.common.model.getTimestampText import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ShareListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ShareListNavLinkView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt index 5156144854..b9b8ea54bc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ShareListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist import SectionItemView import androidx.compose.foundation.layout.* @@ -9,9 +9,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.Indigo -import chat.simplex.app.views.helpers.ProfileImage +import chat.simplex.common.ui.theme.Indigo +import chat.simplex.common.views.helpers.ProfileImage +import chat.simplex.common.model.* @Composable fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index 3a3f929fcc..b3daa610e8 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -1,7 +1,5 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -13,14 +11,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.Chat +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.BackHandler import chat.simplex.res.MR import kotlinx.coroutines.flow.MutableStateFlow @@ -83,7 +80,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState if (chatModel.chats.size >= 8) { barButtons.add { IconButton({ showSearch = true }) { - Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb).capitalize(Locale.current), tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary) } } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt similarity index 90% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 5395c73f37..e8fbb66635 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -1,7 +1,6 @@ -package chat.simplex.app.views.chatlist +package chat.simplex.common.views.chatlist import SectionItemView -import android.util.Log import androidx.compose.animation.core.* import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource @@ -13,19 +12,17 @@ import androidx.compose.ui.* import androidx.compose.ui.draw.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* -import chat.simplex.app.R -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.User +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* @@ -91,10 +88,10 @@ fun UserPicker( } } val xOffset = with(LocalDensity.current) { 10.dp.roundToPx() } - val maxWidth = with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp * density } + val maxWidth = with(LocalDensity.current) { screenWidth() * density } Box(Modifier .fillMaxSize() - .offset { IntOffset(if (newChat.isGone()) -maxWidth.roundToInt() else xOffset, 0) } + .offset { IntOffset(if (newChat.isGone()) -maxWidth.value.roundToInt() else xOffset, 0) } .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { userPickerState.value = AnimatedViewState.HIDING }) .padding(bottom = 10.dp, top = 10.dp) .graphicsLayer { @@ -168,9 +165,9 @@ fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues ) { UserProfileRow(u) if (u.activeUser) { - Icon(painterResource(MR.images.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) } else if (u.hidden) { - Icon(painterResource(MR.images.ic_lock), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) + Icon(painterResource(MR.images.ic_lock), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) } else if (unreadCount > 0) { Box( contentAlignment = Alignment.Center @@ -197,7 +194,7 @@ fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues fun UserProfileRow(u: User) { Row( Modifier - .widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.7f) + .widthIn(max = screenWidth() * 0.7f) .padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt similarity index 57% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index e7782f4d43..fe2a48c1ac 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -1,46 +1,40 @@ -package chat.simplex.app.views.database +package chat.simplex.common.views.database import SectionBottomSpacer import SectionTextFooter import SectionView -import android.content.res.Configuration -import android.net.Uri -import android.util.Log -import android.widget.Toast -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview -import chat.simplex.app.* -import chat.simplex.app.model.ChatModel -import chat.simplex.app.platform.getFilesDirectory -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import kotlinx.datetime.* -import java.io.BufferedOutputStream import java.io.File +import java.net.URI import java.text.SimpleDateFormat import java.util.* @Composable fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTime: Instant) { - val context = LocalContext.current - val archivePath = "${getFilesDirectory()}/$archiveName" - val saveArchiveLauncher = rememberSaveArchiveLauncher(archivePath) + val archivePath = getFilesDirectory() + File.separator + archiveName + val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? -> + if (to != null) { + copyFileToFile(File(archivePath), to) {} + } + } ChatArchiveLayout( title, archiveTime, - saveArchive = { saveArchiveLauncher.launch(archivePath.substringAfterLast("/")) }, + saveArchive = { withApi { saveArchiveLauncher.launch(archivePath.substringAfterLast("/")) }}, deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) } ) } @@ -80,29 +74,6 @@ fun ChatArchiveLayout( } } -@Composable -private fun rememberSaveArchiveLauncher(chatArchivePath: String): ManagedActivityResultLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument(), - onResult = { destination -> - val cxt = SimplexApp.context - try { - destination?.let { - val contentResolver = cxt.contentResolver - contentResolver.openOutputStream(destination)?.let { stream -> - val outputStream = BufferedOutputStream(stream) - File(chatArchivePath).inputStream().use { it.copyTo(outputStream) } - outputStream.close() - Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() - } - } - } catch (e: Error) { - Toast.makeText(cxt, generalGetString(MR.strings.error_saving_file), Toast.LENGTH_SHORT).show() - Log.e(TAG, "rememberSaveArchiveLauncher error saving archive $e") - } - } - ) - private fun deleteArchiveAlert(m: ChatModel, archivePath: String) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.delete_chat_archive_question), @@ -121,12 +92,11 @@ private fun deleteArchiveAlert(m: ChatModel, archivePath: String) { ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewChatArchiveLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseEncryptionView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index 07be9dcef7..137d7f6fd5 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.database +package chat.simplex.common.views.database import SectionBottomSpacer import SectionItemView @@ -23,13 +23,12 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.* -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.* -import chat.simplex.app.R -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* import chat.simplex.res.MR import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.datetime.Clock diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index 7dabbfde12..ab29816c81 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -1,10 +1,9 @@ -package chat.simplex.app.views.database +package chat.simplex.common.views.database import SectionBottomSpacer import SectionSpacer import SectionView -import android.content.Context -import android.util.Log +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions @@ -13,18 +12,13 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.* -import chat.simplex.app.R -import chat.simplex.app.model.AppPreferences -import chat.simplex.app.model.NotificationsMode -import chat.simplex.app.platform.initChatController -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.AppVersionText +import chat.simplex.common.model.AppPreferences +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.AppVersionText import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* @@ -43,7 +37,6 @@ fun DatabaseErrorView( val dbKey = remember { mutableStateOf("") } var storedDBKey by remember { mutableStateOf(DatabaseUtils.ksDatabasePassword.get()) } var useKeychain by remember { mutableStateOf(appPreferences.storeDBPassphrase.get()) } - val context = LocalContext.current val restoreDbFromBackup = remember { mutableStateOf(shouldShowRestoreDbButton(appPreferences)) } fun callRunChat(confirmMigrations: MigrationConfirmation? = null) { @@ -207,11 +200,7 @@ private fun runChat( progressIndicator.value = false when (val status = chatDbStatus.value) { is DBMigrationResult.OK -> { - SimplexService.cancelPassphraseNotification() - when (prefs.notificationsMode.get()) { - NotificationsMode.SERVICE.name -> CoroutineScope(Dispatchers.Default).launch { SimplexService.start() } - NotificationsMode.PERIODIC.name -> SimplexApp.context.schedulePeriodicWakeUp() - } + platform.androidChatStartedAfterBeingOff() } is DBMigrationResult.ErrorNotADatabase -> AlertManager.shared.showAlertMsg(generalGetString(MR.strings.wrong_passphrase_title), generalGetString(MR.strings.enter_correct_passphrase)) @@ -229,12 +218,12 @@ private fun runChat( } private fun shouldShowRestoreDbButton(prefs: AppPreferences): Boolean { - val context = SimplexApp.context val startedAt = prefs.encryptionStartedAt.get() ?: return false /** Just in case there is any small difference between reported Java's [Clock.System.now] and Linux's time on a file */ val safeDiffInTime = 10_000L - val filesChat = File(context.dataDir.absolutePath + File.separator + "files_chat.db.bak") - val filesAgent = File(context.dataDir.absolutePath + File.separator + "files_agent.db.bak") + // LALAL CHANGE FILENAME + val filesChat = File(dataDir.absolutePath + File.separator + "files_chat.db.bak") + val filesAgent = File(dataDir.absolutePath + File.separator + "files_agent.db.bak") return filesChat.exists() && filesAgent.exists() && startedAt.toEpochMilliseconds() - safeDiffInTime <= filesChat.lastModified() && @@ -242,9 +231,9 @@ private fun shouldShowRestoreDbButton(prefs: AppPreferences): Boolean { } private fun restoreDb(restoreDbFromBackup: MutableState, prefs: AppPreferences) { - val context = SimplexApp.context - val filesChatBase = context.dataDir.absolutePath + File.separator + "files_chat.db" - val filesAgentBase = context.dataDir.absolutePath + File.separator + "files_agent.db" + // LALAL CHANGE FILENAME + val filesChatBase = dataDir.absolutePath + File.separator + "files_chat.db" + val filesAgentBase = dataDir.absolutePath + File.separator + "files_agent.db" try { Files.copy(Path("$filesChatBase.bak"), Path(filesChatBase), StandardCopyOption.REPLACE_EXISTING) Files.copy(Path("$filesAgentBase.bak"), Path(filesAgentBase), StandardCopyOption.REPLACE_EXISTING) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index b01ccd9856..2b86d67e17 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -1,17 +1,11 @@ -package chat.simplex.app.views.database +package chat.simplex.common.views.database import SectionBottomSpacer import SectionDividerSpaced import SectionTextFooter import SectionItemView import SectionView -import android.content.res.Configuration -import android.net.Uri -import android.util.Log -import android.widget.Toast -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -20,24 +14,21 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.* +import chat.simplex.common.platform.* import chat.simplex.res.MR -import kotlinx.coroutines.* import kotlinx.datetime.* -import org.apache.commons.io.IOUtils import java.io.* +import java.net.URI +import java.nio.file.Files import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList @@ -47,7 +38,6 @@ fun DatabaseView( m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit) ) { - val context = LocalContext.current val progressIndicator = remember { mutableStateOf(false) } val runChat = remember { m.chatRunning } val prefs = m.controller.appPrefs @@ -56,11 +46,18 @@ fun DatabaseView( val chatArchiveTime = remember { mutableStateOf(prefs.chatArchiveTime.get()) } val chatLastStart = remember { mutableStateOf(prefs.chatLastStart.get()) } val chatArchiveFile = remember { mutableStateOf(null) } - val saveArchiveLauncher = rememberSaveArchiveLauncher(chatArchiveFile) + val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? -> + val file = chatArchiveFile.value + if (file != null && to != null) { + copyFileToFile(File(file), to) { + chatArchiveFile.value = null + } + } + } val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(getAppFilesDirectory())) } - val importArchiveLauncher = rememberGetContentLauncher { uri: Uri? -> - if (uri != null) { - importArchiveAlert(m, uri, appFilesCountAndSize, progressIndicator) + val importArchiveLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + importArchiveAlert(m, to, appFilesCountAndSize, progressIndicator) } } LaunchedEffect(m.chatRunning) { @@ -127,7 +124,7 @@ fun DatabaseLayout( useKeyChain: Boolean, chatDbEncrypted: Boolean?, initialRandomDBPassphrase: SharedPreference, - importArchiveLauncher: ManagedActivityResultLauncher, + importArchiveLauncher: FileChooserLauncher, chatArchiveName: MutableState, chatArchiveTime: MutableState, chatLastStart: MutableState, @@ -202,7 +199,7 @@ fun DatabaseLayout( SettingsActionItem( painterResource(MR.images.ic_download), stringResource(MR.strings.import_database), - { importArchiveLauncher.launch("application/zip") }, + { withApi { importArchiveLauncher.launch("application/zip") }}, textColor = Color.Red, iconColor = Color.Red, disabled = operationsDisabled @@ -374,10 +371,7 @@ private fun startChat(m: ChatModel, runChat: MutableState, chatLastSta val ts = Clock.System.now() m.controller.appPrefs.chatLastStart.set(ts) chatLastStart.value = ts - when (m.controller.appPrefs.notificationsMode.get()) { - NotificationsMode.SERVICE.name -> CoroutineScope(Dispatchers.Default).launch { SimplexService.start() } - NotificationsMode.PERIODIC.name -> SimplexApp.context.schedulePeriodicWakeUp() - } + platform.androidChatStartedAfterBeingOff() } catch (e: Error) { runChat.value = false AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_starting_chat), e.toString()) @@ -431,8 +425,7 @@ private fun stopChat(m: ChatModel, runChat: MutableState) { try { runChat.value = false stopChatAsync(m) - SimplexService.safeStopService(SimplexApp.context) - MessagesFetcherWorker.cancelAll() + platform.androidChatStopped() } catch (e: Error) { runChat.value = true AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString()) @@ -457,7 +450,7 @@ private fun exportArchive( chatArchiveName: MutableState, chatArchiveTime: MutableState, chatArchiveFile: MutableState, - saveArchiveLauncher: ManagedActivityResultLauncher + saveArchiveLauncher: FileChooserLauncher ) { progressIndicator.value = true withApi { @@ -482,8 +475,8 @@ private suspend fun exportChatArchive( val archiveTime = Clock.System.now() val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant())) val archiveName = "simplex-chat.$ts.zip" - val archivePath = "${getFilesDirectory()}/$archiveName" - val config = ArchiveConfig(archivePath, parentTempDirectory = SimplexApp.context.cacheDir.toString()) + val archivePath = "${getFilesDirectory()}${File.separator}$archiveName" + val config = ArchiveConfig(archivePath, parentTempDirectory = cacheDir.toString()) m.controller.apiExportArchive(config) deleteOldArchive(m) m.controller.appPrefs.chatArchiveName.set(archiveName) @@ -497,7 +490,7 @@ private suspend fun exportChatArchive( private fun deleteOldArchive(m: ChatModel) { val chatArchiveName = m.controller.appPrefs.chatArchiveName.get() if (chatArchiveName != null) { - val file = File("${getFilesDirectory()}/$chatArchiveName") + val file = File("${getFilesDirectory()}${File.separator}$chatArchiveName") val fileDeleted = file.delete() if (fileDeleted) { m.controller.appPrefs.chatArchiveName.set(null) @@ -508,39 +501,9 @@ private fun deleteOldArchive(m: ChatModel) { } } -@Composable -private fun rememberSaveArchiveLauncher(chatArchiveFile: MutableState): ManagedActivityResultLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument(), - onResult = { destination -> - val cxt = SimplexApp.context - try { - destination?.let { - val filePath = chatArchiveFile.value - if (filePath != null) { - val contentResolver = SimplexApp.context.contentResolver - contentResolver.openOutputStream(destination)?.let { stream -> - val outputStream = BufferedOutputStream(stream) - File(filePath).inputStream().use { it.copyTo(outputStream) } - outputStream.close() - Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(cxt, generalGetString(MR.strings.file_not_found), Toast.LENGTH_SHORT).show() - } - } - } catch (e: Error) { - Toast.makeText(cxt, generalGetString(MR.strings.error_saving_file), Toast.LENGTH_SHORT).show() - Log.e(TAG, "rememberSaveArchiveLauncher error saving archive $e") - } finally { - chatArchiveFile.value = null - } - } - ) - private fun importArchiveAlert( m: ChatModel, - importedArchiveUri: Uri, + importedArchiveURI: URI, appFilesCountAndSize: MutableState>, progressIndicator: MutableState ) { @@ -548,25 +511,25 @@ private fun importArchiveAlert( title = generalGetString(MR.strings.import_database_question), text = generalGetString(MR.strings.your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one), confirmText = generalGetString(MR.strings.import_database_confirmation), - onConfirm = { importArchive(m, importedArchiveUri, appFilesCountAndSize, progressIndicator) }, + onConfirm = { importArchive(m, importedArchiveURI, appFilesCountAndSize, progressIndicator) }, destructive = true, ) } private fun importArchive( m: ChatModel, - importedArchiveUri: Uri, + importedArchiveURI: URI, appFilesCountAndSize: MutableState>, progressIndicator: MutableState ) { progressIndicator.value = true - val archivePath = saveArchiveFromUri(importedArchiveUri) + val archivePath = saveArchiveFromURI(importedArchiveURI) if (archivePath != null) { withApi { try { m.controller.apiDeleteStorage() try { - val config = ArchiveConfig(archivePath, parentTempDirectory = SimplexApp.context.cacheDir.toString()) + val config = ArchiveConfig(archivePath, parentTempDirectory = cacheDir.toString()) val archiveErrors = m.controller.apiImportArchive(config) DatabaseUtils.ksDatabasePassword.remove() appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory()) @@ -595,21 +558,21 @@ private fun importArchive( } } -private fun saveArchiveFromUri(importedArchiveUri: Uri): String? { +private fun saveArchiveFromURI(importedArchiveURI: URI): String? { return try { - val inputStream = SimplexApp.context.contentResolver.openInputStream(importedArchiveUri) - val archiveName = getFileName(importedArchiveUri) + val inputStream = importedArchiveURI.inputStream() + val archiveName = getFileName(importedArchiveURI) if (inputStream != null && archiveName != null) { - val archivePath = "${SimplexApp.context.cacheDir}/$archiveName" + val archivePath = "$cacheDir${File.separator}$archiveName" val destFile = File(archivePath) - IOUtils.copy(inputStream, FileOutputStream(destFile)) + Files.copy(inputStream, destFile.toPath()) archivePath } else { - Log.e(TAG, "saveArchiveFromUri null inputStream") + Log.e(TAG, "saveArchiveFromURI null inputStream") null } } catch (e: Exception) { - Log.e(TAG, "saveArchiveFromUri error: ${e.message}") + Log.e(TAG, "saveArchiveFromURI error: ${e.message}") null } } @@ -666,7 +629,7 @@ private fun setCiTTL( private fun afterSetCiTTL( m: ChatModel, progressIndicator: MutableState, - appFilesCountAndSize: MutableState> + appFilesCountAndSize: MutableState>, ) { progressIndicator.value = false appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory()) @@ -701,12 +664,11 @@ private fun operationEnded(m: ChatModel, progressIndicator: MutableState Unit, ) { showAlert { - Dialog(onDismissRequest = this::hideAlert) { + DefaultDialog(onDismissRequest = ::hideAlert) { Column( Modifier .background(MaterialTheme.colors.surface, RoundedCornerShape(corner = CornerSize(25.dp))) @@ -211,4 +208,4 @@ private fun alertText(text: String?): (@Composable () -> Unit)? { ) }) } -} \ No newline at end of file +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/AnimationUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt similarity index 87% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/AnimationUtils.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt index 15f507af62..6a400295ed 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/AnimationUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.animation.core.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt similarity index 87% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt index 37bf8dd9dd..3a799eddf8 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt @@ -1,5 +1,6 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape @@ -12,13 +13,11 @@ import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.ChatInfo -import chat.simplex.app.platform.base64ToBitmap -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.common.model.ChatInfo +import chat.simplex.common.platform.base64ToBitmap +import chat.simplex.common.ui.theme.SimpleXTheme import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource @@ -26,7 +25,7 @@ import dev.icerock.moko.resources.ImageResource fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) { val icon = if (chatInfo is ChatInfo.Group) MR.images.ic_supervised_user_circle_filled - else MR.images.ic_account_circle_filled + else MR.images.ic_account_circle_filled ProfileImage(size, chatInfo.image, icon, iconColor) } @@ -71,7 +70,7 @@ fun ProfileImage( ) } } else { - val imageBitmap = base64ToBitmap(image).asImageBitmap() + val imageBitmap = base64ToBitmap(image) Image( imageBitmap, stringResource(MR.strings.image_descr_profile_image), diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChooseAttachmentView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChooseAttachmentView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt index 38b2f0e219..5eee8d99d2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ChooseAttachmentView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt @@ -1,18 +1,14 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.views.newchat.ActionButton +import chat.simplex.common.views.newchat.ActionButton import chat.simplex.res.MR sealed class AttachmentOption { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index b7d42f493d..0877cb42e9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -11,10 +10,10 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import chat.simplex.app.ui.theme.* +import chat.simplex.common.ui.theme.* @Composable fun CloseSheetBar(close: (() -> Unit)?, endButtons: @Composable RowScope.() -> Unit = {}) { @@ -63,12 +62,11 @@ fun AppBarTitle(title: String, withPadding: Boolean = true, bottomPadding: Dp = ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewCloseSheetBar() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CustomIcons.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CustomIcons.kt similarity index 99% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CustomIcons.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CustomIcons.kt index 1ed7d3a954..e3ce25bd3a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CustomIcons.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CustomIcons.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.material.icons.materialIcon import androidx.compose.material.icons.materialPath diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CustomTimePicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CustomTimePicker.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CustomTimePicker.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CustomTimePicker.kt index 32c52dc1d5..fb1df940d9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/CustomTimePicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CustomTimePicker.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -12,9 +12,9 @@ import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.model.CustomTimeUnit +import chat.simplex.common.model.timeText import chat.simplex.res.MR import com.sd.lib.compose.wheel_picker.* @@ -150,7 +150,7 @@ fun CustomTimePickerDialog( confirmButtonAction: (Int) -> Unit, cancel: () -> Unit ) { - Dialog(onDismissRequest = cancel) { + DefaultDialog(onDismissRequest = cancel) { Surface( shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DataClasses.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DataClasses.kt similarity index 82% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DataClasses.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DataClasses.kt index 9fdf2b3966..351a7e9d44 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DataClasses.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DataClasses.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers interface ValueTitle { val value: T diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DatabaseUtils.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index f200d4ab69..4c7481ec55 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -1,19 +1,13 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.util.Log -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.views.usersettings.Cryptor +import chat.simplex.common.model.* +import chat.simplex.common.platform.* import kotlinx.serialization.* import java.io.File import java.security.SecureRandom object DatabaseUtils { - private val cryptor = Cryptor() - - private val appPreferences: AppPreferences by lazy { - ChatController.appPrefs - } + private val appPreferences: AppPreferences = ChatController.appPrefs private const val DATABASE_PASSWORD_ALIAS: String = "databasePassword" private const val APP_PASSWORD_ALIAS: String = "appPassword" @@ -26,16 +20,16 @@ object DatabaseUtils { class KeyStoreItem(private val alias: String, val passphrase: SharedPreference, val initVector: SharedPreference) { fun get(): String? { return cryptor.decryptData( - passphrase.get()?.toByteArrayFromBase64() ?: return null, - initVector.get()?.toByteArrayFromBase64() ?: return null, + passphrase.get()?.toByteArrayFromBase64ForPassphrase() ?: return null, + initVector.get()?.toByteArrayFromBase64ForPassphrase() ?: return null, alias, ) } fun set(key: String) { val data = cryptor.encryptText(key, alias) - passphrase.set(data.first.toBase64String()) - initVector.set(data.second.toBase64String()) + passphrase.set(data.first.toBase64StringForPassphrase()) + initVector.set(data.second.toBase64StringForPassphrase()) } fun remove() { @@ -45,6 +39,7 @@ object DatabaseUtils { } } + // LALAL CHANGE DB FILE NAME ON DESKTOP private fun hasDatabase(rootDir: String): Boolean = File(rootDir + File.separator + "files_chat.db").exists() && File(rootDir + File.separator + "files_agent.db").exists() @@ -53,7 +48,7 @@ object DatabaseUtils { var dbKey = "" val useKeychain = appPreferences.storeDBPassphrase.get() if (useKeychain) { - if (!hasDatabase(SimplexApp.context.dataDir.absolutePath)) { + if (!hasDatabase(dataDir.absolutePath)) { dbKey = randomDatabasePassword() ksDatabasePassword.set(dbKey) appPreferences.initialRandomDBPassphrase.set(true) @@ -67,7 +62,7 @@ object DatabaseUtils { private fun randomDatabasePassword(): String { val s = ByteArray(32) SecureRandom().nextBytes(s) - return s.toBase64String().replace("\n", "") + return s.toBase64StringForPassphrase().replace("\n", "") } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultBasicTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultBasicTextField.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt index 538ce4d86a..65eb11321e 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultBasicTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource @@ -21,8 +21,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.views.database.PassphraseStrength -import chat.simplex.app.views.database.validKey +import chat.simplex.common.views.database.PassphraseStrength +import chat.simplex.common.views.database.validKey import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.kt new file mode 100644 index 0000000000..7e916e0ea7 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.kt @@ -0,0 +1,9 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.runtime.Composable + +@Composable +expect fun DefaultDialog( + onDismissRequest: () -> Unit, + content: @Composable () -> Unit +) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt similarity index 70% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt index 7db325a110..0ae69d82f6 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -11,6 +11,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +expect interface DefaultExposedDropdownMenuBoxScope { + @Composable + open fun DefaultExposedDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit + ) +} + +@Composable +expect fun DefaultExposedDropdownMenuBox( + expanded: Boolean, + onExpandedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + content: @Composable DefaultExposedDropdownMenuBoxScope.() -> Unit +) + @Composable fun DefaultDropdownMenu( showMenu: MutableState, @@ -23,7 +41,7 @@ fun DefaultDropdownMenu( DropdownMenu( expanded = showMenu.value, onDismissRequest = { showMenu.value = false }, - Modifier + modifier = Modifier .widthIn(min = 250.dp) .background(MaterialTheme.colors.surface) .padding(vertical = 4.dp), @@ -35,7 +53,7 @@ fun DefaultDropdownMenu( } @Composable -fun ExposedDropdownMenuBoxScope.DefaultExposedDropdownMenu( +fun DefaultExposedDropdownMenuBoxScope.DefaultExposedDropdownMenu( expanded: MutableState, modifier: Modifier = Modifier, dropdownMenuItems: (@Composable () -> Unit)? @@ -43,7 +61,7 @@ fun ExposedDropdownMenuBoxScope.DefaultExposedDropdownMenu( MaterialTheme( shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(corner = CornerSize(25.dp))) ) { - ExposedDropdownMenu( + DefaultExposedDropdownMenu( modifier = Modifier .widthIn(min = 200.dp) .background(MaterialTheme.colors.surface) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultSwitch.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultSwitch.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultSwitch.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultSwitch.kt index 613caed54d..75abc67b46 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultSwitch.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultSwitch.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material.* diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index 44a56c441d..0162ac7e78 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -1,6 +1,5 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import chat.simplex.app.R import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -11,7 +10,7 @@ import androidx.compose.ui.graphics.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.ui.theme.* +import chat.simplex.common.ui.theme.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Enums.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt similarity index 61% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Enums.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt index cad5d4c53b..67f82e5279 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Enums.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt @@ -1,18 +1,18 @@ @file:UseSerializers(UriSerializer::class) -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.net.Uri import androidx.compose.runtime.saveable.Saver import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import java.net.URI sealed class SharedContent { data class Text(val text: String): SharedContent() - data class Media(val text: String, val uris: List): SharedContent() - data class File(val text: String, val uri: Uri): SharedContent() + data class Media(val text: String, val uris: List): SharedContent() + data class File(val text: String, val uri: URI): SharedContent() } enum class AnimatedViewState { @@ -37,16 +37,16 @@ enum class AnimatedViewState { } -@Serializer(forClass = Uri::class) -object UriSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: Uri) = encoder.encodeString(value.toString()) - override fun deserialize(decoder: Decoder): Uri = Uri.parse(decoder.decodeString()) +@Serializer(forClass = URI::class) +object UriSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("URI", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: URI) = encoder.encodeString(value.toString()) + override fun deserialize(decoder: Decoder): URI = URI(decoder.decodeString()) } @Serializable sealed class UploadContent { - @Serializable data class SimpleImage(val uri: Uri): UploadContent() - @Serializable data class AnimatedImage(val uri: Uri): UploadContent() - @Serializable data class Video(val uri: Uri, val duration: Int): UploadContent() + @Serializable data class SimpleImage(val uri: URI): UploadContent() + @Serializable data class AnimatedImage(val uri: URI): UploadContent() + @Serializable data class Video(val uri: URI, val duration: Int): UploadContent() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ExposedDropDownSettingRow.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt index eb563dbd0f..2f24ff4144 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -12,10 +12,9 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.usersettings.SettingsActionItemWithContent import chat.simplex.res.MR +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.usersettings.SettingsActionItemWithContent @Composable fun ExposedDropDownSettingRow( @@ -30,7 +29,7 @@ fun ExposedDropDownSettingRow( ) { SettingsActionItemWithContent(icon, title, iconColor = iconTint, disabled = !enabled.value) { val expanded = remember { mutableStateOf(false) } - ExposedDropdownMenuBox( + DefaultExposedDropdownMenuBox( expanded = expanded.value, onExpandedChange = { expanded.value = !expanded.value && enabled.value diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GestureDetector.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GestureDetector.kt index daf323dc0d..4df48ca0af 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GestureDetector.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.util.Log +import chat.simplex.common.platform.Log import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.* @@ -28,7 +28,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.* import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.Density -import chat.simplex.app.TAG +import chat.simplex.common.platform.TAG import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlin.math.PI diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GetImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GetImageView.kt new file mode 100644 index 0000000000..308038ec5e --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GetImageView.kt @@ -0,0 +1,13 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.runtime.* +import androidx.compose.ui.graphics.ImageBitmap +import java.net.URI + +@Composable +expect fun GetImageBottomSheet( + imageBitmap: MutableState, + onImageChange: (ImageBitmap) -> Unit, + hideBottomSheet: () -> Unit +) + diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt similarity index 88% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt index 3f7135309f..3d59aac446 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt @@ -1,7 +1,6 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.content.res.Configuration -import android.graphics.BitmapFactory +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -10,19 +9,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.LinkPreview -import chat.simplex.app.platform.base64ToBitmap -import chat.simplex.app.platform.resizeImageToStrSize -import chat.simplex.app.ui.theme.* +import chat.simplex.common.model.LinkPreview +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -60,11 +55,11 @@ suspend fun getLinkPreview(url: String): LinkPreview? { imageUri = normalizeImageUri(u, imageUri) try { val stream = URL(imageUri).openStream() - val image = resizeImageToStrSize(BitmapFactory.decodeStream(stream), maxDataSize = 14000) -// TODO add once supported in iOS -// val description = ogTags.firstOrNull { -// it.attr("property") == "og:description" -// }?.attr("content") ?: "" + val image = resizeImageToStrSize(stream.use(::loadImageBitmap), maxDataSize = 14000) + // TODO add once supported in iOS + // val description = ogTags.firstOrNull { + // it.attr("property") == "og:description" + // }?.attr("content") ?: "" if (title != null) { return@withContext LinkPreview(url, title, description = "", image) } @@ -98,7 +93,7 @@ fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit, cancel ) } } else { - val imageBitmap = base64ToBitmap(linkPreview.image).asImageBitmap() + val imageBitmap = base64ToBitmap(linkPreview.image) Image( imageBitmap, stringResource(MR.strings.image_descr_link_preview), @@ -129,7 +124,7 @@ fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit, cancel fun ChatItemLinkView(linkPreview: LinkPreview) { Column { Image( - base64ToBitmap(linkPreview.image).asImageBitmap(), + base64ToBitmap(linkPreview.image), stringResource(MR.strings.image_descr_link_preview), modifier = Modifier.fillMaxWidth(), contentScale = ContentScale.FillWidth, @@ -176,12 +171,11 @@ private fun normalizeImageUri(u: URL, imageUri: String) = when { } }*/ -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "ChatItemLinkView (Dark Mode)" -) +)*/ @Composable fun PreviewChatItemLinkView() { SimpleXTheme { @@ -189,12 +183,11 @@ fun PreviewChatItemLinkView() { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "ComposeLinkView (Dark Mode)" -) +)*/ @Composable fun PreviewComposeLinkView() { SimpleXTheme { @@ -202,7 +195,7 @@ fun PreviewComposeLinkView() { } } -@Preview(showBackground = true) +@Preview @Composable fun PreviewComposeLinkViewLoading() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt similarity index 63% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt index 3b1250a75e..b6b512e03a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/LocalAuthentication.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt @@ -1,12 +1,14 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.ui.Modifier -import chat.simplex.app.SimplexApp -import chat.simplex.app.views.localauth.LocalAuthView +import chat.simplex.common.model.ChatController +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.BackHandler +import chat.simplex.common.views.localauth.LocalAuthView +import chat.simplex.common.views.usersettings.LAMode import chat.simplex.res.MR sealed class LAResult { @@ -28,19 +30,28 @@ data class LocalAuthRequest ( } } +expect fun authenticate( + promptTitle: String, + promptSubtitle: String, + selfDestruct: Boolean = false, + usingLAMode: LAMode = ChatModel.controller.appPrefs.laMode.get(), + completed: (LAResult) -> Unit +) + fun authenticateWithPasscode( promptTitle: String, promptSubtitle: String, selfDestruct: Boolean, - completed: (LAResult) -> Unit) { + completed: (LAResult) -> Unit +) { val password = DatabaseUtils.ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password))) - ModalManager.shared.showPasscodeCustomModal { close -> + ModalManager.shared.showPasscodeCustomModal { close -> BackHandler { close() completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled))) } Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - LocalAuthView(SimplexApp.context.chatModel, LocalAuthRequest(promptTitle, promptSubtitle, password, selfDestruct && SimplexApp.context.chatModel.controller.appPrefs.selfDestruct.get()) { + LocalAuthView(ChatModel, LocalAuthRequest(promptTitle, promptSubtitle, password, selfDestruct && ChatController.appPrefs.selfDestruct.get()) { close() completed(it) }) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ModalView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 724f0907fa..598ceaab8c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -1,24 +1,15 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.R -import android.util.Log -import androidx.activity.compose.BackHandler import androidx.compose.animation.* import androidx.compose.animation.core.* import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.capitalize -import androidx.compose.ui.text.intl.Locale -import chat.simplex.app.TAG -import chat.simplex.app.ui.theme.isInDarkTheme -import chat.simplex.app.ui.theme.themedBackground +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.themedBackground import java.util.concurrent.atomic.AtomicBoolean @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Modifiers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Modifiers.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Modifiers.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Modifiers.kt index f60d644a88..6990a69ebd 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Modifiers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Modifiers.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.offset diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt index a7afb9441f..4b6c70df44 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource @@ -24,7 +24,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R import chat.simplex.res.MR import kotlinx.coroutines.delay diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Section.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index 5c7a40e282..377eb7a8ae 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -8,16 +8,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.ValueTitleDesc -import chat.simplex.app.views.helpers.ValueTitle -import chat.simplex.app.views.usersettings.SettingsActionItemWithContent +import chat.simplex.common.platform.screenWidth +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.ValueTitleDesc +import chat.simplex.common.views.helpers.ValueTitle +import chat.simplex.common.views.usersettings.SettingsActionItemWithContent import chat.simplex.res.MR @Composable @@ -238,13 +238,13 @@ fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color @Composable fun InfoRowEllipsis(title: String, value: String, onClick: () -> Unit) { SectionItemViewSpaceBetween(onClick) { - val configuration = LocalConfiguration.current + val screenWidthDp = screenWidth() Text(title) Text( value, Modifier .padding(start = 10.dp) - .widthIn(max = (configuration.screenWidthDp / 2).dp), + .widthIn(max = (screenWidthDp / 2)), maxLines = 1, overflow = TextOverflow.Ellipsis, color = MaterialTheme.colors.secondary diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SimpleButton.kt similarity index 94% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SimpleButton.kt index 6202ded6de..5ab0e68c6b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SimpleButton.kt @@ -1,5 +1,6 @@ -package chat.simplex.app.ui.theme +package chat.simplex.common.views.helpers +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -13,8 +14,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import chat.simplex.common.ui.theme.SimpleXTheme import chat.simplex.res.MR @Composable @@ -90,7 +91,7 @@ fun SimpleButtonFrame(click: () -> Unit, modifier: Modifier = Modifier, disabled @Preview @Composable -fun PreviewCloseSheetBar() { +fun PreviewShareButton() { SimpleXTheme { SimpleButton(text = "Share", icon = painterResource(MR.images.ic_share), click = {}) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt similarity index 87% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt index f0cdfaec8b..7ebbb7e5cb 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt @@ -1,11 +1,9 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.util.Log import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -13,16 +11,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.* import androidx.compose.ui.graphics.* -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.* -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.platform.chatParseMarkdown -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.MarkdownText -import com.google.accompanist.insets.navigationBarsWithImePadding +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.model.FormattedText +import chat.simplex.common.platform.* import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.serialization.Serializable import java.lang.Exception @@ -54,7 +49,7 @@ fun TextEditor( .fillMaxWidth() .padding(contentPadding) .heightIn(min = 52.dp), -// .border(border = BorderStroke(1.dp, strokeColor), shape = RoundedCornerShape(26.dp)), + // .border(border = BorderStroke(1.dp, strokeColor), shape = RoundedCornerShape(26.dp)), contentAlignment = Alignment.Center, ) { val modifier = modifier diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt similarity index 65% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 1f04855cbd..74bee1f114 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/helpers/Utils.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -1,32 +1,23 @@ -package chat.simplex.app.views.helpers +package chat.simplex.common.views.helpers -import android.app.Activity -import android.content.ActivityNotFoundException -import android.graphics.Bitmap -import android.net.Uri -import android.util.Base64 -import android.util.Log import androidx.compose.runtime.* import androidx.compose.runtime.saveable.Saver -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.UriHandler -import androidx.compose.ui.unit.IntSize -import androidx.core.graphics.ColorUtils -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.getAppFilesDirectory -import chat.simplex.app.platform.resizeImageToDataSize -import chat.simplex.app.ui.theme.ThemeOverrides +import androidx.compose.ui.graphics.* +import androidx.compose.ui.platform.* +import androidx.compose.ui.text.* +import androidx.compose.ui.unit.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.ThemeOverrides import chat.simplex.res.MR import com.charleskorn.kaml.decodeFromStream import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString -import org.apache.commons.io.IOUtils import java.io.* +import java.net.URI +import java.nio.file.Files import java.text.SimpleDateFormat import java.util.* import kotlin.math.* @@ -43,9 +34,21 @@ enum class KeyboardState { Opened, Closed } +// Resource to annotated string from +// https://stackoverflow.com/questions/68549248/android-jetpack-compose-how-to-show-styled-text-from-string-resources fun generalGetString(id: StringResource): String { // prefer stringResource in Composable items to retain preview abilities - return id.getString(SimplexApp.context) + return id.localized() +} + +expect fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString + +@Composable +fun annotatedStringResource(id: StringResource): AnnotatedString { + val density = LocalDensity.current + return remember(id) { + escapedHtmlToAnnotatedString(id.localized(), density) + } } // maximum image file size to be auto-accepted @@ -60,12 +63,23 @@ const val MAX_FILE_SIZE_SMP: Long = 8000000 const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824 // 1GB -fun getAppFileUri(fileName: String): Uri { - return Uri.parse("${getAppFilesDirectory()}/$fileName") -} +expect fun getAppFileUri(fileName: String): URI -fun getThemeFromUri(uri: Uri, withAlertOnException: Boolean = true): ThemeOverrides? { - SimplexApp.context.contentResolver.openInputStream(uri).use { +// https://developer.android.com/training/data-storage/shared/documents-files#bitmap +expect fun getLoadedImage(file: CIFile?): ImageBitmap? + +expect fun getFileName(uri: URI): String? + +expect fun getAppFilePath(uri: URI): String? + +expect fun getFileSize(uri: URI): Long? + +expect fun getBitmapFromUri(uri: URI, withAlertOnException: Boolean = true): ImageBitmap? + +expect fun getDrawableFromUri(uri: URI, withAlertOnException: Boolean = true): Any? + +fun getThemeFromUri(uri: URI, withAlertOnException: Boolean = true): ThemeOverrides? { + uri.inputStream().use { runCatching { return yaml.decodeFromStream(it!!) }.onFailure { @@ -80,17 +94,17 @@ fun getThemeFromUri(uri: Uri, withAlertOnException: Boolean = true): ThemeOverri return null } -fun saveImage(uri: Uri): String? { +fun saveImage(uri: URI): String? { val bitmap = getBitmapFromUri(uri) ?: return null return saveImage(bitmap) } -fun saveImage(image: Bitmap): String? { +fun saveImage(image: ImageBitmap): String? { return try { - val ext = if (image.hasAlpha()) "png" else "jpg" + val ext = if (image.hasAlpha) "png" else "jpg" val dataResized = resizeImageToDataSize(image, ext == "png", maxDataSize = MAX_IMAGE_SIZE) val fileToSave = generateNewFileName("IMG", ext) - val file = File(chat.simplex.app.platform.getAppFilePath(fileToSave)) + val file = File(getAppFilePath(fileToSave)) val output = FileOutputStream(file) dataResized.writeTo(output) output.flush() @@ -102,7 +116,7 @@ fun saveImage(image: Bitmap): String? { } } -fun saveAnimImage(uri: Uri): String? { +fun saveAnimImage(uri: URI): String? { return try { val filename = getFileName(uri)?.lowercase() var ext = when { @@ -113,11 +127,11 @@ fun saveAnimImage(uri: Uri): String? { // Just in case the image has a strange extension if (ext.length < 3 || ext.length > 4) ext = "gif" val fileToSave = generateNewFileName("IMG", ext) - val file = File(chat.simplex.app.platform.getAppFilePath(fileToSave)) + val file = File(getAppFilePath(fileToSave)) val output = FileOutputStream(file) - SimplexApp.context.contentResolver.openInputStream(uri)!!.use { input -> + uri.inputStream().use { input -> output.use { output -> - input.copyTo(output) + input?.copyTo(output) } } fileToSave @@ -127,15 +141,16 @@ fun saveAnimImage(uri: Uri): String? { } } +expect suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean): File? -fun saveFileFromUri(uri: Uri): String? { +fun saveFileFromUri(uri: URI): String? { return try { - val inputStream = SimplexApp.context.contentResolver.openInputStream(uri) + val inputStream = uri.inputStream() val fileToSave = getFileName(uri) if (inputStream != null && fileToSave != null) { val destFileName = uniqueCombine(fileToSave) - val destFile = File(chat.simplex.app.platform.getAppFilePath(destFileName)) - IOUtils.copy(inputStream, FileOutputStream(destFile)) + val destFile = File(getAppFilePath(destFileName)) + Files.copy(inputStream, destFile.toPath()) destFileName } else { Log.e(TAG, "Util.kt saveFileFromUri null inputStream") @@ -161,7 +176,7 @@ fun uniqueCombine(fileName: String): String { fun tryCombine(n: Int): String { val suffix = if (n == 0) "" else "_$n" val f = "$name$suffix.$ext" - return if (File(chat.simplex.app.platform.getAppFilePath(f)).exists()) tryCombine(n + 1) else f + return if (File(getAppFilePath(f)).exists()) tryCombine(n + 1) else f } return tryCombine(0) } @@ -185,7 +200,7 @@ fun formatBytes(bytes: Long): String { } fun removeFile(fileName: String): Boolean { - val file = File(chat.simplex.app.platform.getAppFilePath(fileName)) + val file = File(getAppFilePath(fileName)) val fileDeleted = file.delete() if (!fileDeleted) { Log.e(TAG, "Util.kt removeFile error") @@ -212,7 +227,7 @@ fun directoryFileCountAndSize(dir: String): Pair { // count, size in fileCount++ bytes += it.length() } - } catch (e: java.lang.Exception) { + } catch (e: Exception) { Log.e(TAG, "Util directoryFileCountAndSize error: ${e.stackTraceToString()}") } return fileCount to bytes @@ -225,18 +240,34 @@ fun getMaxFileSize(fileProtocol: FileProtocol): Long { } } +expect fun getBitmapFromVideo(uri: URI, timestamp: Long? = null, random: Boolean = true): VideoPlayerInterface.PreviewAndDuration + fun Color.darker(factor: Float = 0.1f): Color = Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha) fun Color.lighter(factor: Float = 0.1f): Color = Color(min(red * (1 + factor), 1f), min(green * (1 + factor), 1f), min(blue * (1 + factor), 1f), alpha) -fun Color.mixWith(color: Color, alpha: Float): Color = - Color(ColorUtils.blendARGB(color.toArgb(), toArgb(), alpha)) +fun Color.mixWith(color: Color, alpha: Float): Color = blendARGB(color, this, alpha) -fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT) +fun blendARGB( + color1: Color, color2: Color, + ratio: Float +): Color { + val inverseRatio = 1 - ratio + val a: Float = color1.alpha * inverseRatio + color2.alpha * ratio + val r: Float = color1.red * inverseRatio + color2.red * ratio + val g: Float = color1.green * inverseRatio + color2.green * ratio + val b: Float = color1.blue * inverseRatio + color2.blue * ratio + return Color(r, g, b, a) +} -fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT) +expect fun ByteArray.toBase64StringForPassphrase(): String + +// Android's default implementation that was used before multiplatform, adds non-needed characters at the end of string +// which can be bypassed by: +// fun String.toByteArrayFromBase64(): ByteArray = Base64.getDecoder().decode(this.trimEnd { it == '\n' || it == ' ' }) +expect fun String.toByteArrayFromBase64ForPassphrase(): ByteArray val LongRange.Companion.saver get() = Saver, Pair>( @@ -253,7 +284,7 @@ inline fun serializableSaver(): Saver = Saver( fun UriHandler.openUriCatching(uri: String) { try { openUri(uri) - } catch (e: ActivityNotFoundException) { + } catch (e: Exception/*ActivityNotFoundException*/) { Log.e(TAG, e.stackTraceToString()) } } @@ -265,14 +296,12 @@ fun IntSize.Companion.Saver(): Saver = Saver( @Composable fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) { - val context = LocalContext.current DisposableEffect(Unit) { always() - val activity = context as? Activity ?: return@DisposableEffect onDispose {} - val orientation = activity.resources.configuration.orientation + val orientation = screenOrientation() onDispose { whenDispose() - if (orientation == activity.resources.configuration.orientation) { + if (orientation == screenOrientation()) { whenGone() } } @@ -281,14 +310,12 @@ fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {} @Composable fun DisposableEffectOnRotate(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenRotate: () -> Unit) { - val context = LocalContext.current DisposableEffect(Unit) { always() - val activity = context as? Activity ?: return@DisposableEffect onDispose {} - val orientation = activity.resources.configuration.orientation + val orientation = screenOrientation() onDispose { whenDispose() - if (orientation != activity.resources.configuration.orientation) { + if (orientation != screenOrientation()) { whenRotate() } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt similarity index 81% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/LocalAuthView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index f3281b38e0..0719c5da37 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -1,21 +1,19 @@ -package chat.simplex.app.views.localauth +package chat.simplex.common.views.localauth -import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.platform.initChatController -import chat.simplex.app.views.database.deleteChatAsync -import chat.simplex.app.views.database.stopChatAsync -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.helpers.DatabaseUtils.ksSelfDestructPassword -import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword -import chat.simplex.app.views.onboarding.OnboardingStage +import chat.simplex.common.views.database.deleteChatAsync +import chat.simplex.common.views.database.stopChatAsync +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.helpers.DatabaseUtils.ksSelfDestructPassword +import chat.simplex.common.views.helpers.DatabaseUtils.ksAppPassword +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.Profile +import chat.simplex.common.platform.* import chat.simplex.res.MR -import kotlinx.coroutines.delay @Composable fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) { @@ -44,7 +42,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( deleteChatAsync(m) ksAppPassword.set(password) ksSelfDestructPassword.remove() - m.controller.ntfManager.cancelAllNotifications() + ntfManager.cancelAllNotifications() val selfDestructPref = m.controller.appPrefs.selfDestruct val displayNamePref = m.controller.appPrefs.selfDestructDisplayName val displayName = displayNamePref.get() diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/PasscodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/PasscodeView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt index 42186f20c9..3e35bfb80a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/PasscodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt @@ -1,19 +1,18 @@ -package chat.simplex.app.views.localauth +package chat.simplex.common.views.localauth -import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.SimpleButton -import chat.simplex.app.views.helpers.* +import chat.simplex.common.platform.ScreenOrientation +import chat.simplex.common.platform.screenOrientation +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.SimpleButton +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable @@ -91,7 +90,7 @@ fun PasscodeView( } } - if (LocalContext.current.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { + if (screenOrientation() == ScreenOrientation.PORTRAIT) { VerticalLayout() } else { HorizontalLayout() diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/PasswordEntry.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasswordEntry.kt similarity index 99% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/PasswordEntry.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasswordEntry.kt index 98a880b4f0..ad7c26deb7 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/PasswordEntry.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasswordEntry.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.localauth +package chat.simplex.common.views.localauth import androidx.compose.foundation.background import androidx.compose.foundation.clickable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/SetAppPasscodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/SetAppPasscodeView.kt similarity index 84% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/SetAppPasscodeView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/SetAppPasscodeView.kt index 5bac848f76..18437dbf98 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/localauth/SetAppPasscodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/SetAppPasscodeView.kt @@ -1,12 +1,11 @@ -package chat.simplex.app.views.localauth +package chat.simplex.common.views.localauth -import androidx.activity.compose.BackHandler import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import chat.simplex.app.R -import chat.simplex.app.views.helpers.DatabaseUtils -import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.platform.BackHandler +import chat.simplex.common.views.helpers.DatabaseUtils +import chat.simplex.common.views.helpers.DatabaseUtils.ksAppPassword +import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt similarity index 74% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactLearnMore.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt index dd1963921d..2913f6ac79 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt @@ -1,14 +1,13 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import androidx.compose.foundation.* import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.R -import chat.simplex.app.views.helpers.AppBarTitle -import chat.simplex.app.views.onboarding.ReadableText -import chat.simplex.app.views.onboarding.ReadableTextWithLink +import chat.simplex.common.views.helpers.AppBarTitle +import chat.simplex.common.views.onboarding.ReadableText +import chat.simplex.common.views.onboarding.ReadableTextWithLink import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt index fb987be6bb..e7de6154ea 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt @@ -1,9 +1,9 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import SectionBottomSpacer import SectionSpacer import SectionView -import android.content.res.Configuration +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -12,22 +12,23 @@ import dev.icerock.moko.resources.compose.painterResource import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.SettingsActionItem +import chat.simplex.common.platform.shareText +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR @Composable fun AddContactView(connReqInvitation: String, connIncognito: Boolean) { + val clipboard = LocalClipboardManager.current AddContactLayout( connReq = connReqInvitation, connIncognito = connIncognito, - share = { shareText(connReqInvitation) }, + share = { clipboard.shareText(connReqInvitation) }, learnMore = { ModalManager.shared.showModal { Column( @@ -152,12 +153,11 @@ fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean } } -@Preview -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewAddContactView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt similarity index 86% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 7b54b7ba2e..c89e0e43fb 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -1,6 +1,6 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat -import android.net.Uri +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -15,27 +15,23 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.platform.cropToSquare -import chat.simplex.app.platform.resizeImageToStrSize -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.ProfileNameField -import chat.simplex.app.views.chat.group.AddGroupMembersView -import chat.simplex.app.views.chatlist.setGroupMembers -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.isValidDisplayName -import chat.simplex.app.views.onboarding.ReadableText -import chat.simplex.app.views.usersettings.DeleteImageButton -import chat.simplex.app.views.usersettings.EditImageButton -import com.google.accompanist.insets.ProvideWindowInsets -import com.google.accompanist.insets.navigationBarsWithImePadding +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.ProfileNameField +import chat.simplex.common.views.chat.group.AddGroupMembersView +import chat.simplex.common.views.chatlist.setGroupMembers +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.isValidDisplayName +import chat.simplex.common.views.onboarding.ReadableText +import chat.simplex.common.views.usersettings.DeleteImageButton +import chat.simplex.common.views.usersettings.EditImageButton +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.net.URI @Composable fun AddGroupView(chatModel: ChatModel, close: () -> Unit) { @@ -65,7 +61,7 @@ fun AddGroupLayout(createGroup: (GroupProfile) -> Unit, close: () -> Unit) { val scope = rememberCoroutineScope() val displayName = rememberSaveable { mutableStateOf("") } val fullName = rememberSaveable { mutableStateOf("") } - val chosenImage = rememberSaveable { mutableStateOf(null) } + val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(null) } val focusRequester = remember { FocusRequester() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ConnectViaLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ConnectViaLinkView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt index 15ada1e103..5418ddc02e 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ConnectViaLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -8,8 +8,8 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel +import chat.simplex.common.model.ChatModel +import chat.simplex.common.views.newchat.ScanToConnectView import chat.simplex.res.MR enum class ConnectViaLinkTab { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt similarity index 85% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 95501c2a88..07d937823b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -1,9 +1,9 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import SectionBottomSpacer import SectionDividerSpaced import SectionView -import android.content.res.Configuration +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -11,16 +11,18 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalClipboardManager import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview -import chat.simplex.app.model.* -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.LocalAliasEditor -import chat.simplex.app.views.chatlist.deleteContactConnectionAlert -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.SettingsActionItem +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.LocalAliasEditor +import chat.simplex.common.views.chatlist.deleteContactConnectionAlert +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.SettingsActionItem +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.PendingContactConnection +import chat.simplex.common.platform.shareText import chat.simplex.res.MR @Composable @@ -45,6 +47,7 @@ fun ContactConnectionInfoView( } } } + val clipboard = LocalClipboardManager.current ContactConnectionInfoLayout( connReq = connReqInvitation, contactConnection, @@ -52,7 +55,7 @@ fun ContactConnectionInfoView( focusAlias, deleteConnection = { deleteContactConnectionAlert(contactConnection, chatModel, close) }, onLocalAliasChanged = { setContactAlias(contactConnection, it, chatModel) }, - share = { if (connReqInvitation != null) shareText(connReqInvitation) }, + share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) }, learnMore = { ModalManager.shared.showModal { Column( @@ -137,12 +140,11 @@ private fun setContactAlias(contactConnection: PendingContactConnection, localAl } } -@Preview -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable private fun PreviewContactConnectionInfoView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt index b0b7b923d7..3d6cfb2f91 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -9,12 +9,10 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.ModalManager -import chat.simplex.app.views.helpers.withApi -import chat.simplex.app.views.usersettings.UserAddressView +import chat.simplex.common.model.ChatModel +import chat.simplex.common.views.helpers.ModalManager +import chat.simplex.common.views.helpers.withApi +import chat.simplex.common.views.usersettings.UserAddressView import chat.simplex.res.MR enum class CreateLinkTab { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index fd7edeaa29..32d5986515 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -1,8 +1,9 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat -import androidx.activity.compose.BackHandler +import chat.simplex.common.platform.BackHandler import androidx.compose.animation.* import androidx.compose.animation.core.* +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* @@ -20,14 +21,12 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp -import androidx.core.graphics.ColorUtils -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.screenWidth +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -91,11 +90,11 @@ private fun NewChatSheetLayout( } } } - val maxWidth = with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp * density } + val maxWidth = with(LocalDensity.current) { screenWidth() * density } Column( Modifier .fillMaxSize() - .offset { IntOffset(if (newChat.isGone()) -maxWidth.roundToInt() else 0, 0) } + .offset { IntOffset(if (newChat.isGone()) -maxWidth.value.roundToInt() else 0, 0) } .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) { closeNewChatSheet(true) } .drawBehind { drawRect(animatedColor.value) }, verticalArrangement = Arrangement.Bottom, @@ -103,7 +102,7 @@ private fun NewChatSheetLayout( ) { val actions = remember { listOf(addContact, connectViaLink, createGroup) } val backgroundColor = if (isInDarkTheme()) - Color(ColorUtils.blendARGB(MaterialTheme.colors.primary.toArgb(), Color.Black.toArgb(), 0.7F)) + blendARGB(MaterialTheme.colors.primary, Color.Black, 0.7F) else MaterialTheme.colors.background LazyColumn(Modifier diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt similarity index 82% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt index f7f1770abd..ba8b8df548 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt @@ -1,43 +1,38 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import SectionBottomSpacer -import android.content.ClipboardManager -import android.content.res.Configuration -import android.net.Uri -import android.util.Log +import androidx.compose.desktop.ui.tooling.preview.Preview +import chat.simplex.common.platform.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat.getSystemService -import chat.simplex.app.R -import chat.simplex.app.TAG -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.platform.TAG +import chat.simplex.common.model.ChatModel +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import java.net.URI @Composable fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) { val connectionLink = remember { mutableStateOf("") } - val context = LocalContext.current - val clipboard = getSystemService(context, ClipboardManager::class.java) + val clipboard = LocalClipboardManager.current PasteToConnectLayout( chatModel.incognito.value, connectionLink = connectionLink, pasteFromClipboard = { - connectionLink.value = clipboard?.primaryClip?.getItemAt(0)?.coerceToText(context) as? String ?: return@PasteToConnectLayout + connectionLink.value = clipboard.getText()?.text ?: return@PasteToConnectLayout }, connectViaLink = { connReqUri -> try { - val uri = Uri.parse(connReqUri) + val uri = URI(connReqUri) withUriAction(uri) { linkType -> val action = suspend { Log.d(TAG, "connectViaUri: connecting") @@ -114,11 +109,10 @@ fun PasteToConnectLayout( } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode" -) +)*/ @Composable fun PreviewPasteToConnectTextbox() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt similarity index 82% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index 0d48a68abe..9882caa55d 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/QRCode.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -1,6 +1,6 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat -import android.graphics.Bitmap +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.runtime.* @@ -8,15 +8,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.* import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.core.graphics.* import boofcv.alg.drawing.FiducialImageEngine import boofcv.alg.fiducial.qrcode.* -import boofcv.android.ConvertBitmap -import chat.simplex.app.platform.addLogo -import chat.simplex.app.platform.shareFile -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.launch @@ -34,7 +30,6 @@ fun QRCode( val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) { qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) .let { if (withLogo) it.addLogo() else it } - .asImageBitmap() } Image( bitmap = qr, @@ -54,7 +49,7 @@ fun QRCode( } } -fun qrCodeBitmap(content: String, size: Int = 1024): Bitmap { +fun qrCodeBitmap(content: String, size: Int = 1024): ImageBitmap { val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ val numModules = QrCode.totalModules(qrCode.version) @@ -68,9 +63,11 @@ fun qrCodeBitmap(content: String, size: Int = 1024): Bitmap { val renderer = QrCodeGeneratorImage(pixelsPerModule + 1) renderer.borderModule = borderModule renderer.render(qrCode) - return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565).scale(size, size) + return renderer.gray.toImageBitmap().scale(size, size) } +expect fun ImageBitmap.replaceColor(from: Int, to: Int): ImageBitmap + @Preview @Composable fun PreviewQRCode() { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt new file mode 100644 index 0000000000..66ba595e17 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt @@ -0,0 +1,6 @@ +package chat.simplex.common.views.newchat + +import androidx.compose.runtime.* + +@Composable +expect fun QRCodeScanner(onBarcode: (String) -> Unit) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt similarity index 82% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt index 9c37a34207..b6bcf96749 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt @@ -1,9 +1,8 @@ -package chat.simplex.app.views.newchat +package chat.simplex.common.views.newchat import SectionBottomSpacer -import android.content.res.Configuration -import android.net.Uri -import android.util.Log +import androidx.compose.desktop.ui.tooling.preview.Preview +import chat.simplex.common.platform.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -11,25 +10,27 @@ import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.core.net.toUri -import chat.simplex.app.TAG -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.json -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.* +import chat.simplex.common.platform.TAG +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.json +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import java.net.URI + +@Composable +expect fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) @Composable fun QRCodeScanner(close: () -> Unit) { QRCodeScanner { connReqUri -> try { - val uri = Uri.parse(connReqUri) + val uri = URI(connReqUri) withUriAction(uri) { linkType -> val action = suspend { Log.d(TAG, "connectViaUri: connecting") @@ -64,9 +65,9 @@ sealed class CReqClientData { @Serializable @SerialName("group") data class Group(val groupLinkId: String): CReqClientData() } -fun withUriAction(uri: Uri, run: suspend (ConnectionLinkType) -> Unit) { +fun withUriAction(uri: URI, run: suspend (ConnectionLinkType) -> Unit) { val action = uri.path?.drop(1)?.replace("/", "") - val data = uri.toString().replaceFirst("#/", "/").toUri().getQueryParameter("data") + val data = URI(uri.toString().replaceFirst("#/", "/")).getQueryParameter("data") val type = when { data != null -> { val parsed = runCatching { @@ -77,6 +78,7 @@ fun withUriAction(uri: Uri, run: suspend (ConnectionLinkType) -> Unit) { else -> null } } + action == "contact" -> ConnectionLinkType.CONTACT action == "invitation" -> ConnectionLinkType.INVITATION else -> null @@ -91,7 +93,7 @@ fun withUriAction(uri: Uri, run: suspend (ConnectionLinkType) -> Unit) { } } -suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: Uri): Boolean { +suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: URI): Boolean { val r = chatModel.controller.apiConnect(uri.toString()) if (r) { AlertManager.shared.showAlertMsg( @@ -134,18 +136,22 @@ fun ConnectContactLayout(chatModelIncognito: Boolean, close: () -> Unit) { } } -@Preview -@Preview( +fun URI.getQueryParameter(param: String): String? { + if (!query.contains("$param=")) return null + return query.substringAfter("$param=").substringBefore("&") +} + +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewConnectContactLayout() { SimpleXTheme { ConnectContactLayout( chatModelIncognito = false, - close = {} + close = {}, ) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index 6c632160dc..b8eb064b8c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -1,7 +1,6 @@ -package chat.simplex.app.views.onboarding +package chat.simplex.common.views.onboarding import SectionBottomSpacer -import android.util.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -9,30 +8,32 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalUriHandler import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import chat.simplex.app.* -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.UserContactLinkRec -import chat.simplex.app.platform.sendEmail -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCode +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.UserContactLinkRec +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCode import chat.simplex.res.MR @Composable fun CreateSimpleXAddress(m: ChatModel) { var progressIndicator by remember { mutableStateOf(false) } val userAddress = remember { m.userAddress } + val clipboard = LocalClipboardManager.current + val uriHandler = LocalUriHandler.current CreateSimpleXAddressLayout( userAddress.value, - share = { address: String -> shareText(address) }, + share = { address: String -> clipboard.shareText(address) }, sendEmail = { address -> - sendEmail( + uriHandler.sendEmail( generalGetString(MR.strings.email_invite_subject), generalGetString(MR.strings.email_invite_body).format(address.connReqContact) ) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index c8bc7b5edf..a506c8daff 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -1,6 +1,6 @@ -package chat.simplex.app.views.onboarding +package chat.simplex.common.views.onboarding -import android.content.res.Configuration +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -8,19 +8,18 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler +import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.User -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.MarkdownText -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ChatController +import chat.simplex.common.model.User +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.MarkdownText +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource -import dev.icerock.moko.resources.compose.stringResource @Composable fun HowItWorks(user: User?, onboardingStage: MutableState? = null) { @@ -87,16 +86,15 @@ fun ReadableMarkdownText(text: String, textAlign: TextAlign = TextAlign.Start, p formattedText = remember(text) { parseToMarkdown(text) }, modifier = Modifier.padding(padding), style = TextStyle(textAlign = textAlign, lineHeight = 22.sp, fontSize = 16.sp), - linkMode = SimplexApp.context.chatModel.controller.appPrefs.simplexLinkMode.get(), + linkMode = ChatController.appPrefs.simplexLinkMode.get(), ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewHowItWorks() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt similarity index 82% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt index ac37eff8d8..e3190f8756 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt @@ -1,14 +1,14 @@ -package chat.simplex.app.views.onboarding +package chat.simplex.common.views.onboarding import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import chat.simplex.app.model.ChatModel -import chat.simplex.app.views.CreateProfilePanel -import chat.simplex.app.platform.getKeyboardState -import com.google.accompanist.insets.ProvideWindowInsets +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.ProvideWindowInsets +import chat.simplex.common.views.CreateProfilePanel +import chat.simplex.common.platform.getKeyboardState import kotlinx.coroutines.launch enum class OnboardingStage { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt similarity index 90% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index b76629952f..af640d5b48 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.onboarding +package chat.simplex.common.views.onboarding import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -13,10 +13,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.usersettings.changeNotificationsMode +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.NotificationsMode +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.changeNotificationsMode import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource @@ -49,6 +50,9 @@ fun SetNotificationsMode(m: ChatModel) { SetNotificationsModeAdditions() } +@Composable +expect fun SetNotificationsModeAdditions() + @Composable private fun NotificationButton(currentMode: MutableState, mode: NotificationsMode, title: StringResource, description: StringResource) { TextButton( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index 9c47931a40..ddb1e81ec4 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -1,6 +1,6 @@ -package chat.simplex.app.views.onboarding +package chat.simplex.common.views.onboarding -import android.content.res.Configuration +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -14,14 +14,10 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.* -import chat.simplex.app.R -import chat.simplex.app.SimplexApp -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.User -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.ModalManager +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource @@ -132,7 +128,7 @@ fun OnboardingActionButton( onclick?.invoke() onboardingStage.value = onboarding if (onboarding != null) { - SimplexApp.context.chatModel.controller.appPrefs.onboardingStage.set(onboarding) + ChatController.appPrefs.onboardingStage.set(onboarding) } }, modifier) { Text(stringResource(labelId), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary, fontSize = 20.sp) @@ -143,12 +139,11 @@ fun OnboardingActionButton( } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewSimpleXInfo() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 923b47eb83..38e7d8f986 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -1,8 +1,5 @@ -package chat.simplex.app.views.onboarding +package chat.simplex.common.views.onboarding -import android.content.res.Configuration -import android.os.Build -import androidx.annotation.IntegerRes import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -14,19 +11,15 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalUriHandler import dev.icerock.moko.resources.compose.painterResource -import androidx.compose.ui.res.stringResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.BuildConfig -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource @@ -393,12 +386,11 @@ fun shouldShowWhatsNew(m: ChatModel): Boolean { return v != lastVersion } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewWhatsNewView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/AdvancedNetworkSettings.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index 0c2341c69b..863266b387 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -1,9 +1,10 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionCustomFooter import SectionItemView import SectionView +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -17,14 +18,12 @@ import androidx.compose.ui.graphics.painter.Painter import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.ChatModel import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource import java.text.DecimalFormat @Composable @@ -254,7 +253,7 @@ fun IntSettingRow(title: String, selection: MutableState, values: List Text(title) - ExposedDropdownMenuBox( + DefaultExposedDropdownMenuBox( expanded = expanded.value, onExpandedChange = { expanded.value = !expanded.value @@ -313,7 +312,7 @@ fun TimeoutSettingRow(title: String, selection: MutableState, values: List Text(title) - ExposedDropdownMenuBox( + DefaultExposedDropdownMenuBox( expanded = expanded.value, onExpandedChange = { expanded.value = !expanded.value @@ -407,7 +406,7 @@ fun showUpdateNetworkSettingsDialog(action: () -> Unit) { ) } -@Preview(showBackground = true) +@Preview @Composable fun PreviewAdvancedNetworkSettingsLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt similarity index 77% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 5fe8ddc318..e1e35ace8d 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Appearance.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -1,39 +1,37 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionItemView import SectionItemViewSpaceBetween import SectionSpacer import SectionView -import android.net.Uri -import android.util.Log -import android.widget.Toast -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* +import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import chat.simplex.app.SimplexApp -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.res.MR -import com.godaddy.android.colorpicker.ClassicColorPicker -import com.godaddy.android.colorpicker.HsvColor +import androidx.compose.ui.graphics.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* +import chat.simplex.res.MR +import com.godaddy.android.colorpicker.* import kotlinx.serialization.encodeToString -import java.io.BufferedOutputStream +import java.io.File +import java.net.URI import java.util.* import kotlin.collections.ArrayList +@Composable +expect fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) + object AppearanceScope { @Composable fun ThemesSection( @@ -70,32 +68,32 @@ object AppearanceScope { SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) { val title = generalGetString(MR.strings.color_primary) Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primary) } SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT, currentTheme.colors.primaryVariant) }) { val title = generalGetString(MR.strings.color_primary_variant) Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.primaryVariant) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primaryVariant) } SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY, currentTheme.colors.secondary) }) { val title = generalGetString(MR.strings.color_secondary) Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.secondary) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondary) } SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT, currentTheme.colors.secondaryVariant) }) { val title = generalGetString(MR.strings.color_secondary_variant) Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.secondaryVariant) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondaryVariant) } SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND, currentTheme.colors.background) }) { val title = generalGetString(MR.strings.color_background) Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.background) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.background) } SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE, currentTheme.colors.surface) }) { val title = generalGetString(MR.strings.color_surface) Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = MaterialTheme.colors.surface) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.surface) } SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE, currentTheme.appColors.title) }) { val title = generalGetString(MR.strings.color_title) @@ -116,32 +114,38 @@ object AppearanceScope { val isInDarkTheme = isInDarkTheme() if (currentTheme.base.hasChangedAnyColor(currentTheme.colors, currentTheme.appColors)) { SectionItemView({ ThemeManager.resetAllThemeColors(darkForSystemTheme = isInDarkTheme) }) { - Text(generalGetString(MR.strings.reset_color), color = MaterialTheme.colors.primary) + Text(generalGetString(MR.strings.reset_color), color = colors.primary) } } SectionSpacer() SectionView { val theme = remember { mutableStateOf(null as String?) } - val exportThemeLauncher = rememberSaveThemeLauncher(theme) + val exportThemeLauncher = rememberFileChooserLauncher(false) { to: URI? -> + val themeValue = theme.value + if (themeValue != null && to != null) { + copyBytesToFile(themeValue.byteInputStream(), to) { + theme.value = null + } + } + } SectionItemView({ val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme) theme.value = yaml.encodeToString(overrides) - exportThemeLauncher.launch("simplex.theme") + withApi { exportThemeLauncher.launch("simplex.theme")} }) { - Text(generalGetString(MR.strings.export_theme), color = MaterialTheme.colors.primary) + Text(generalGetString(MR.strings.export_theme), color = colors.primary) } - - val importThemeLauncher = rememberGetContentLauncher { uri: Uri? -> - if (uri != null) { - val theme = getThemeFromUri(uri) + val importThemeLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + val theme = getThemeFromUri(to) if (theme != null) { ThemeManager.saveAndApplyThemeOverrides(theme, isInDarkTheme) } } } // Can not limit to YAML mime type since it's unsupported by Android - SectionItemView({ importThemeLauncher.launch("*/*") }) { - Text(generalGetString(MR.strings.import_theme), color = MaterialTheme.colors.primary) + SectionItemView({ withApi { importThemeLauncher.launch("*/*") } }) { + Text(generalGetString(MR.strings.import_theme), color = colors.primary) } } SectionBottomSpacer() @@ -181,12 +185,10 @@ object AppearanceScope { @Composable fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { - ClassicColorPicker( - color = initialColor, - modifier = Modifier - .fillMaxWidth() - .height(300.dp), - showAlphaBar = true, + ClassicColorPicker(modifier = Modifier + .fillMaxWidth() + .height(300.dp), + color = HsvColor.from(color = initialColor), showAlphaBar = true, onColorChanged = { color: HsvColor -> onColorChanged(color.toColor()) } @@ -253,36 +255,8 @@ object AppearanceScope { onSelected = { if (it != null) onSelected(it) } ) } - //private fun openSystemLangPicker(activity: Activity) { // activity.startActivity(Intent(Settings.ACTION_APP_LOCALE_SETTINGS, Uri.parse("package:" + SimplexApp.context.packageName))) //} - - @Composable - private fun rememberSaveThemeLauncher(theme: MutableState): ManagedActivityResultLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument(), - onResult = { destination -> - val cxt = SimplexApp.context - try { - destination?.let { - val theme = theme.value - if (theme != null) { - val contentResolver = cxt.contentResolver - contentResolver.openOutputStream(destination)?.let { stream -> - BufferedOutputStream(stream).use { outputStream -> - theme.byteInputStream().use { it.copyTo(outputStream) } - } - Toast.makeText(cxt, generalGetString(MR.strings.file_saved), Toast.LENGTH_SHORT).show() - } - } - } - } catch (e: Error) { - Toast.makeText(cxt, generalGetString(MR.strings.error_saving_file), Toast.LENGTH_SHORT).show() - Log.e(TAG, "rememberSaveThemeLauncher error saving theme $e") - } finally { - theme.value = null - } - } - ) } + diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt index 38d6a88eb8..d4219a51c5 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionItemView @@ -14,9 +14,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt similarity index 90% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/DeveloperView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index a3e51c1dab..c69ba20939 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionTextFooter @@ -12,10 +12,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.views.TerminalView -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.views.TerminalView +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/HelpView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt similarity index 66% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/HelpView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt index 2a736bf38a..c5bde44947 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/HelpView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt @@ -1,19 +1,16 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings -import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview -import chat.simplex.app.R -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.chatlist.ChatHelpView -import chat.simplex.app.views.helpers.AppBarTitle +import androidx.compose.desktop.ui.tooling.preview.Preview +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.views.chatlist.ChatHelpView +import chat.simplex.common.views.helpers.AppBarTitle import chat.simplex.res.MR @Composable @@ -34,12 +31,11 @@ fun HelpLayout(userDisplayName: String) { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewHelpView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/HiddenProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt similarity index 89% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/HiddenProfileView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt index 8c266b8975..215899d0ff 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/HiddenProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionItemView @@ -15,13 +15,12 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.User -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chatlist.UserProfileRow -import chat.simplex.app.views.database.PassphraseField -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.User +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chatlist.UserProfileRow +import chat.simplex.common.views.database.PassphraseField +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/IncognitoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt similarity index 81% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/IncognitoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt index b7aeb0a802..4728c0f2b7 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/IncognitoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import androidx.compose.foundation.* @@ -8,10 +8,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.AppBarTitle -import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.AppBarTitle +import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/MarkdownHelpView.kt similarity index 84% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/MarkdownHelpView.kt index aa405ef15c..ab2df2014c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/MarkdownHelpView.kt @@ -1,23 +1,19 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer -import android.content.res.Configuration import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.Format -import chat.simplex.app.model.FormatColor -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.common.model.Format +import chat.simplex.common.model.FormatColor +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.SimpleXTheme import chat.simplex.res.MR @Composable @@ -87,12 +83,11 @@ fun appendColor(b: AnnotatedString.Builder, s: String, c: FormatColor, after: St b.append(after) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewMarkdownHelpView() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 72292d80ba..344d002aa0 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionCustomFooter @@ -20,14 +20,15 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.* import androidx.compose.ui.text.input.* -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.ClickableText -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.ClickableText +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.views.helpers.annotatedStringResource import chat.simplex.res.MR @Composable @@ -434,7 +435,7 @@ private fun showUpdateNetworkSettingsDialog( ) } -@Preview(showBackground = true) +@Preview @Composable fun PreviewNetworkAndServersLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt similarity index 71% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt index af58e98dff..6c59bb1508 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt @@ -1,9 +1,8 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionView import SectionViewSelectable -import android.os.Build import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -14,23 +13,12 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow -import chat.simplex.app.* -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.NotificationsMode -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR -import kotlinx.coroutines.* import kotlin.collections.ArrayList -enum class NotificationPreviewMode { - MESSAGE, CONTACT, HIDDEN; - - companion object { - val default: NotificationPreviewMode = MESSAGE - } -} - @Composable fun NotificationsSettingsView( chatModel: ChatModel, @@ -41,14 +29,14 @@ fun NotificationsSettingsView( } NotificationsSettingsLayout( - notificationsMode = chatModel.notificationsMode, + notificationsMode = remember { chatModel.controller.appPrefs.notificationsMode.state }, notificationPreviewMode = chatModel.notificationPreviewMode, showPage = { page -> ModalManager.shared.showModalCloseable(true) { - when (page) { - CurrentPage.NOTIFICATIONS_MODE -> NotificationsModeView(chatModel.notificationsMode) { changeNotificationsMode(it, chatModel) } - CurrentPage.NOTIFICATION_PREVIEW_MODE -> NotificationPreviewView(chatModel.notificationPreviewMode, onNotificationPreviewModeSelected) - } + when (page) { + CurrentPage.NOTIFICATIONS_MODE -> NotificationsModeView(chatModel.controller.appPrefs.notificationsMode.state) { changeNotificationsMode(it, chatModel) } + CurrentPage.NOTIFICATION_PREVIEW_MODE -> NotificationPreviewView(chatModel.notificationPreviewMode, onNotificationPreviewModeSelected) + } } }, ) @@ -72,13 +60,15 @@ fun NotificationsSettingsLayout( ) { AppBarTitle(stringResource(MR.strings.notifications)) SectionView(null) { - SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notifications_mode_title), { showPage(CurrentPage.NOTIFICATIONS_MODE) }) { - Text( - modes.first { it.value == notificationsMode.value }.title, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colors.secondary - ) + if (appPlatform == AppPlatform.ANDROID) { + SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notifications_mode_title), { showPage(CurrentPage.NOTIFICATIONS_MODE) }) { + Text( + modes.first { it.value == notificationsMode.value }.title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colors.secondary + ) + } } SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notification_preview_mode_title), { showPage(CurrentPage.NOTIFICATION_PREVIEW_MODE) }) { Text( @@ -176,21 +166,6 @@ fun notificationPreviewModes(): List> { } fun changeNotificationsMode(mode: NotificationsMode, chatModel: ChatModel) { - chatModel.controller.appPrefs.notificationsMode.set(mode.name) - if (mode.requiresIgnoringBattery && !SimplexService.isIgnoringBatteryOptimizations()) { - chatModel.controller.appPrefs.backgroundServiceNoticeShown.set(false) - } - chatModel.notificationsMode.value = mode - SimplexService.StartReceiver.toggleReceiver(mode == NotificationsMode.SERVICE) - CoroutineScope(Dispatchers.Default).launch { - if (mode == NotificationsMode.SERVICE) - SimplexService.start() - else - SimplexService.safeStopService(SimplexApp.context) - } - - if (mode != NotificationsMode.PERIODIC) { - MessagesFetcherWorker.cancelAll() - } - SimplexService.showBackgroundServiceNoticeIfNeeded() + chatModel.controller.appPrefs.notificationsMode.set(mode) + platform.androidNotificationsModeChanged(mode) } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt similarity index 97% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index c6da840f2f..91a1a2ec86 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced @@ -13,9 +13,8 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt similarity index 92% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 3ec12a00e8..9c2c95e037 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -1,33 +1,33 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced import SectionItemView import SectionTextFooter import SectionView -import android.view.WindowManager import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.fragment.app.FragmentActivity -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.ProfileNameField -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.helpers.DatabaseUtils.ksAppPassword -import chat.simplex.app.views.helpers.DatabaseUtils.ksSelfDestructPassword -import chat.simplex.app.views.isValidDisplayName -import chat.simplex.app.views.localauth.SetAppPasscodeView -import chat.simplex.app.views.onboarding.ReadableText import chat.simplex.res.MR +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.ProfileNameField +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.helpers.DatabaseUtils.ksAppPassword +import chat.simplex.common.views.helpers.DatabaseUtils.ksSelfDestructPassword +import chat.simplex.common.views.isValidDisplayName +import chat.simplex.common.views.localauth.SetAppPasscodeView +import chat.simplex.common.views.onboarding.ReadableText +import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.AppPlatform +import chat.simplex.common.platform.appPlatform enum class LAMode { SYSTEM, @@ -38,6 +38,11 @@ enum class LAMode { SYSTEM -> generalGetString(MR.strings.la_mode_system) PASSCODE -> generalGetString(MR.strings.la_mode_passcode) } + + companion object { + val default: LAMode + get() = if (appPlatform == AppPlatform.ANDROID) SYSTEM else PASSCODE + } } @Composable @@ -90,6 +95,12 @@ private fun SimpleXLinkOptions(simplexLinkModeState: State, onS ) } +@Composable +expect fun PrivacyDeviceSection( + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + setPerformLA: (Boolean) -> Unit, +) + private val laDelays = listOf(10, 30, 60, 180, 0) @Composable @@ -102,7 +113,6 @@ fun SimplexLockView( val laMode = remember { chatModel.controller.appPrefs.laMode.state } val laLockDelay = remember { chatModel.controller.appPrefs.laLockDelay } val showChangePasscode = remember { derivedStateOf { performLA.value && currentLAMode.state.value == LAMode.PASSCODE } } - val activity = LocalContext.current as FragmentActivity val selfDestructPref = remember { chatModel.controller.appPrefs.selfDestruct } val selfDestructDisplayName = remember { mutableStateOf(chatModel.controller.appPrefs.selfDestructDisplayName.get() ?: "") } val selfDestructDisplayNamePref = remember { chatModel.controller.appPrefs.selfDestructDisplayName } @@ -114,7 +124,7 @@ fun SimplexLockView( fun disableUnavailableLA() { resetLAEnabled(false) - currentLAMode.set(LAMode.SYSTEM) + currentLAMode.set(LAMode.default) laUnavailableInstructionAlert() } @@ -277,12 +287,14 @@ fun SimplexLockView( setPerformLA(false) } } - LockModeSelector(laMode) { newLAMode -> - if (laMode.value == newLAMode) return@LockModeSelector - if (chatModel.controller.appPrefs.performLA.get()) { - toggleLAMode(newLAMode) - } else { - currentLAMode.set(newLAMode) + if (appPlatform == AppPlatform.ANDROID) { + LockModeSelector(laMode) { newLAMode -> + if (laMode.value == newLAMode) return@LockModeSelector + if (chatModel.controller.appPrefs.performLA.get()) { + toggleLAMode(newLAMode) + } else { + currentLAMode.set(newLAMode) + } } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index a711275ccf..f3896a3de9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -1,11 +1,11 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced import SectionItemView import SectionItemViewSpaceBetween import SectionView -import android.util.Log +import chat.simplex.common.platform.Log import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.selection.SelectionContainer @@ -20,15 +20,14 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.model.ServerAddress.Companion.parseServerAddress -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCode +import chat.simplex.common.platform.TAG +import chat.simplex.common.model.* +import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCode +import chat.simplex.common.model.ChatModel import chat.simplex.res.MR -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.isActive import kotlinx.coroutines.launch diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt similarity index 98% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ProtocolServersView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index 6443bfc3f8..3d4487160c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced @@ -19,11 +19,10 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.model.ServerAddress.Companion.parseServerAddress -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.views.usersettings.ScanProtocolServer import chat.simplex.res.MR import kotlinx.coroutines.launch diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt index f79b968b15..7cb30440d3 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionItemViewSpaceBetween @@ -18,11 +18,10 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.call.parseRTCIceServers -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ChatModel +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.call.parseRTCIceServers +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt similarity index 72% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt index c80c69ab92..02582ec93c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/ScanProtocolServer.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt @@ -1,17 +1,20 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.app.model.ServerAddress.Companion.parseServerAddress -import chat.simplex.app.model.ServerCfg -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCodeScanner +import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress +import chat.simplex.common.model.ServerCfg +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCodeScanner import chat.simplex.res.MR +@Composable +expect fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) + @Composable fun ScanProtocolServerLayout(onNext: (ServerCfg) -> Unit) { Column( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt similarity index 93% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 7c689c71a9..3226dc85be 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/SettingsView.common.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced @@ -6,7 +6,7 @@ import SectionItemView import SectionItemViewWithIcon import SectionView import TextIconSpaced -import android.content.res.Configuration +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -21,18 +21,15 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.* -import androidx.work.WorkManager -import chat.simplex.app.* -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.database.DatabaseView -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.SimpleXInfo -import chat.simplex.app.views.onboarding.WhatsNewView +import chat.simplex.common.model.* +import chat.simplex.common.platform.appVersionInfo +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.database.DatabaseView +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.SimpleXInfo +import chat.simplex.common.views.onboarding.WhatsNewView import chat.simplex.res.MR -import com.jakewharton.processphoenix.ProcessPhoenix @Composable fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { @@ -59,7 +56,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { ModalView( { close() }, endButtons = { - SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = true) { search.value = it } + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } }, content = { modalView(chatModel, search) }) } @@ -184,6 +181,14 @@ fun SettingsLayout( } } +@Composable +expect fun SettingsSectionApp( + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showVersion: () -> Unit, + withAuth: (title: String, desc: String, block: () -> Unit) -> Unit +) + @Composable fun SettingsIncognitoActionItem( incognitoPref: SharedPreference, @@ -266,14 +271,13 @@ fun MaintainIncognitoState(chatModel: ChatModel) { @Composable fun ChatLockItem( - chatModel: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), setPerformLA: (Boolean) -> Unit ) { - val performLA = remember { chatModel.performLA } - val currentLAMode = remember { chatModel.controller.appPrefs.laMode } + val performLA = remember { ChatModel.performLA } + val currentLAMode = remember { ChatModel.controller.appPrefs.laMode } SettingsActionItemWithContent( - click = showSettingsModal { SimplexLockView(chatModel, currentLAMode, setPerformLA) }, + click = showSettingsModal { SimplexLockView(ChatModel, currentLAMode, setPerformLA) }, icon = if (performLA.value) painterResource(MR.images.ic_lock_filled) else painterResource(MR.images.ic_lock), text = stringResource(MR.strings.chat_lock), iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary, @@ -347,12 +351,13 @@ fun ChatLockItem( } } -@Composable fun AppVersionItem(showVersion: () -> Unit) { +@Composable +fun AppVersionItem(showVersion: () -> Unit) { SectionItemViewWithIcon(showVersion) { AppVersionText() } } @Composable fun AppVersionText() { - Text("v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") + Text(appVersionInfo.first + (if (appVersionInfo.second != null) " (" + appVersionInfo.second + ")" else "")) } @Composable fun ProfilePreview(profileOf: NamedChat, size: Dp = 60.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, textColor: Color = MaterialTheme.colors.onBackground, stopped: Boolean = false) { @@ -483,12 +488,11 @@ private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewSettingsLayout() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt similarity index 77% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressLearnMore.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt index de08ffda66..276c595435 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt @@ -1,14 +1,13 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.R -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.ReadableText -import chat.simplex.app.views.onboarding.ReadableTextWithLink +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.ReadableText +import chat.simplex.common.views.onboarding.ReadableTextWithLink import chat.simplex.res.MR @Composable diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt similarity index 95% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 9b60ff465c..d907e0fff5 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -1,12 +1,11 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced import SectionItemView import SectionTextFooter import SectionView -import android.content.res.Configuration -import android.util.Log +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -16,18 +15,19 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalUriHandler import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import chat.simplex.app.TAG -import chat.simplex.app.model.* -import chat.simplex.app.platform.sendEmail -import chat.simplex.app.platform.shareText -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.ShareAddressButton -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.QRCode +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.ShareAddressButton +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.QRCode +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.MsgContent +import chat.simplex.common.platform.* import chat.simplex.res.MR @Composable @@ -57,6 +57,8 @@ fun UserAddressView( } } val userAddress = remember { chatModel.userAddress } + val clipboard = LocalClipboardManager.current + val uriHandler = LocalUriHandler.current val showLayout = @Composable { UserAddressLayout( userAddress = userAddress.value, @@ -94,9 +96,9 @@ fun UserAddressView( } } }, - share = { userAddress: String -> shareText(userAddress) }, + share = { userAddress: String -> clipboard.shareText(userAddress) }, sendEmail = { userAddress -> - sendEmail( + uriHandler.sendEmail( generalGetString(MR.strings.email_invite_subject), generalGetString(MR.strings.email_invite_body).format(userAddress.connReqContact) ) @@ -417,12 +419,11 @@ private fun SaveAASButton(disabled: Boolean, onClick: () -> Unit) { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewUserAddressLayoutNoAddress() { SimpleXTheme { @@ -451,12 +452,11 @@ private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewUserAddressLayoutAddressCreated() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt similarity index 91% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index 9a4dca4125..7f8edbd0fd 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -1,8 +1,7 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer -import android.content.res.Configuration -import android.net.Uri +import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -16,20 +15,19 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.app.model.* -import chat.simplex.app.platform.* -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.ProfileNameField -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.isValidDisplayName -import chat.simplex.app.views.onboarding.ReadableText -import com.google.accompanist.insets.ProvideWindowInsets -import com.google.accompanist.insets.navigationBarsWithImePadding +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.ProfileNameField +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.isValidDisplayName +import chat.simplex.common.views.onboarding.ReadableText +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.Profile +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.launch +import java.net.URI @Composable fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { @@ -62,7 +60,7 @@ fun UserProfileLayout( val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = remember { mutableStateOf(profile.displayName) } val fullName = remember { mutableStateOf(profile.fullName) } - val chosenImage = rememberSaveable { mutableStateOf(null) } + val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(profile.image) } val scope = rememberCoroutineScope() val scrollState = rememberScrollState() @@ -217,12 +215,11 @@ private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { ) } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewUserProfileLayoutEditOff() { SimpleXTheme { @@ -234,12 +231,11 @@ fun PreviewUserProfileLayoutEditOff() { } } -@Preview(showBackground = true) -@Preview( +@Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" -) +)*/ @Composable fun PreviewUserProfileLayoutEditOn() { SimpleXTheme { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt similarity index 96% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index db3174ad9f..b788787854 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -1,4 +1,4 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDivider @@ -20,16 +20,16 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.platform.chatPasswordHash -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.ItemAction -import chat.simplex.app.views.chatlist.UserProfilePickerItem -import chat.simplex.app.views.chatlist.UserProfileRow -import chat.simplex.app.views.database.PassphraseField -import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.onboarding.CreateProfile +import chat.simplex.common.model.* +import chat.simplex.common.platform.chatPasswordHash +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.ItemAction +import chat.simplex.common.views.chatlist.UserProfilePickerItem +import chat.simplex.common.views.chatlist.UserProfileRow +import chat.simplex.common.views.database.PassphraseField +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.CreateProfile +import chat.simplex.common.model.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.delay diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/VersionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt similarity index 55% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/VersionInfoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt index e7eab47993..8528414771 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/usersettings/VersionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt @@ -1,17 +1,16 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.app.BuildConfig -import chat.simplex.app.R -import chat.simplex.app.model.CoreVersionInfo -import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.views.helpers.AppBarTitle +import chat.simplex.common.BuildConfigCommon +import chat.simplex.common.model.CoreVersionInfo +import chat.simplex.common.platform.appPlatform +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.AppBarTitle import chat.simplex.res.MR @Composable @@ -20,8 +19,12 @@ fun VersionInfoView(info: CoreVersionInfo) { Modifier.padding(horizontal = DEFAULT_PADDING), ) { AppBarTitle(stringResource(MR.strings.app_version_title), false) - Text(String.format(stringResource(MR.strings.app_version_name), BuildConfig.VERSION_NAME)) - Text(String.format(stringResource(MR.strings.app_version_code), BuildConfig.VERSION_CODE)) + if (appPlatform.isAndroid) { + Text(String.format(stringResource(MR.strings.app_version_name), BuildConfigCommon.ANDROID_VERSION_NAME)) + Text(String.format(stringResource(MR.strings.app_version_code), BuildConfigCommon.ANDROID_VERSION_CODE)) + } else { + Text(String.format(stringResource(MR.strings.app_version_name), BuildConfigCommon.DESKTOP_VERSION_NAME)) + } Text(String.format(stringResource(MR.strings.core_version), info.version)) val simplexmqCommit = if (info.simplexmqCommit.length >= 7) info.simplexmqCommit.substring(startIndex = 0, endIndex = 7) else info.simplexmqCommit Text(String.format(stringResource(MR.strings.core_simplexmq_version), info.simplexmqVersion, simplexmqCommit)) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt new file mode 100644 index 0000000000..ffe9bd81a6 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -0,0 +1,127 @@ +package chat.simplex.common + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.* +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.* +import chat.simplex.common.model.ChatController +import chat.simplex.common.model.ChatModel +import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.views.helpers.FileDialogChooser +import kotlinx.coroutines.* +import java.awt.event.WindowEvent +import java.awt.event.WindowFocusListener +import java.io.File + +val simplexWindowState = SimplexWindowState() + +fun showApp() = application { + val windowState = rememberWindowState(placement = WindowPlacement.Floating) + Window(state = windowState, onCloseRequest = ::exitApplication, onKeyEvent = { + if (it.key == Key.Escape && it.type == KeyEventType.KeyUp) { + simplexWindowState.backstack.lastOrNull()?.invoke() != null + } else { + false + } + }, title = "SimpleX") { + SimpleXTheme { + AppScreen() + if (simplexWindowState.openDialog.isAwaiting) { + FileDialogChooser( + title = "SimpleX", + isLoad = true, + onResult = { + simplexWindowState.openDialog.onResult(it) + } + ) + } + + if (simplexWindowState.saveDialog.isAwaiting) { + FileDialogChooser( + title = "SimpleX", + isLoad = false, + onResult = { simplexWindowState.saveDialog.onResult(it) } + ) + } + val toasts = remember { simplexWindowState.toasts } + val toast = toasts.firstOrNull() + if (toast != null) { + Box(Modifier.fillMaxSize().padding(bottom = 20.dp), contentAlignment = Alignment.BottomCenter) { + Text( + toast.first, + Modifier.background(MaterialTheme.colors.primary, RoundedCornerShape(100)).padding(vertical = 5.dp, horizontal = 10.dp), + color = MaterialTheme.colors.onPrimary, + style = MaterialTheme.typography.body1 + ) + } + // Shows toast in insertion order with preferred delay per toast. New one will be shown once previous one expires + LaunchedEffect(toast, toasts.size) { + delay(toast.second) + simplexWindowState.toasts.removeFirst() + } + } + } + var windowFocused by remember { mutableStateOf(true) } + LaunchedEffect(windowFocused) { + val delay = ChatController.appPrefs.laLockDelay.get() + if (!windowFocused && ChatModel.performLA.value && delay > 0) { + delay(delay * 1000L) + // Trigger auth state check when delay ends (and if it ends) + AppLock.recheckAuthState() + } + } + LaunchedEffect(Unit) { + window.addWindowFocusListener(object : WindowFocusListener { + override fun windowGainedFocus(p0: WindowEvent?) { + windowFocused = true + AppLock.recheckAuthState() + } + + override fun windowLostFocus(p0: WindowEvent?) { + windowFocused = false + AppLock.appWasHidden() + } + }) + } + } +} + +class SimplexWindowState { + val backstack = mutableStateListOf<() -> Unit>() + val openDialog = DialogState() + val saveDialog = DialogState() + val toasts = mutableStateListOf>() +} + +class DialogState { + private var onResult: CompletableDeferred? by mutableStateOf(null) + val isAwaiting get() = onResult != null + + suspend fun awaitResult(): T { + onResult = CompletableDeferred() + val result = onResult!!.await() + onResult = null + return result + } + + fun onResult(result: T) = onResult!!.complete(result) +} + +@Preview +@Composable +fun AppPreview() { + SimpleXTheme { + AppScreen() + } +} + +/** Needed for [chat.simplex.common.platform.Files] to get path to jar file */ +class DesktopApp() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt new file mode 100644 index 0000000000..826d705e42 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -0,0 +1,67 @@ +package chat.simplex.common.platform + +import chat.simplex.common.DesktopApp +import chat.simplex.common.model.* +import chat.simplex.common.views.call.RcvCallInvitation +import chat.simplex.common.views.helpers.withBGApi +import java.io.* +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes +import java.util.* + +actual val appPlatform = AppPlatform.DESKTOP + +@Suppress("ConstantLocale") +val defaultLocale: Locale = Locale.getDefault() + +fun initApp() { + ntfManager = object : NtfManager() { // LALAL + override fun notifyContactConnected(user: User, contact: Contact) {} + override fun notifyContactRequestReceived(user: User, cInfo: ChatInfo.ContactRequest) {} + override fun notifyMessageReceived(user: User, cInfo: ChatInfo, cItem: ChatItem) {} + override fun notifyCallInvitation(invitation: RcvCallInvitation) {} + override fun hasNotificationsForChat(chatId: String): Boolean = false + override fun cancelNotificationsForChat(chatId: String) {} + override fun displayNotification(user: User, chatId: String, displayName: String, msgText: String, image: String?, actions: List) {} + override fun createNtfChannelsMaybeShowAlert() {} + override fun cancelCallNotification() {} + override fun cancelAllNotifications() {} + } + applyAppLocale() + withBGApi { + initChatController() + runMigrations() + } +} + +private fun applyAppLocale() { + val lang = ChatController.appPrefs.appLanguage.get() + if (lang == null || lang == Locale.getDefault().language) return + Locale.setDefault(Locale.forLanguageTag(lang)) +} + +@Suppress("UnsafeDynamicallyLoadedCode") +actual fun initHaskell() { + val libApp = "libapp-lib.${desktopPlatform.libExtension}" + val tmpDir = Files.createTempDirectory("simplex-native-libs").toFile() + tmpDir.deleteOnExit() + copyResources(desktopPlatform.libPath, tmpDir.toPath()) + System.load(File(tmpDir, libApp).absolutePath) + initHS() +} + +private fun copyResources(from: String, to: Path) { + val resource = DesktopApp::class.java.getResource("")!!.toURI() + val fileSystem = FileSystems.newFileSystem(resource, emptyMap()) + val resPath = fileSystem.getPath(from) + Files.walkFileTree(resPath, object: SimpleFileVisitor() { + override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult { + Files.createDirectories(to.resolve(resPath.relativize(dir).toString())) + return FileVisitResult.CONTINUE + } + override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + Files.copy(file, to.resolve(resPath.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING) + return FileVisitResult.CONTINUE + } + }) +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Back.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Back.desktop.kt new file mode 100644 index 0000000000..7c64cd469a --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Back.desktop.kt @@ -0,0 +1,14 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.* +import chat.simplex.common.simplexWindowState + +@Composable +actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) { + DisposableEffect(enabled) { + if (enabled) simplexWindowState.backstack.add(onBack) + onDispose { + simplexWindowState.backstack.remove(onBack) + } + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Cryptor.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Cryptor.desktop.kt new file mode 100644 index 0000000000..870fde945b --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Cryptor.desktop.kt @@ -0,0 +1,15 @@ +package chat.simplex.common.platform + +actual val cryptor: CryptorInterface = object : CryptorInterface { + override fun decryptData(data: ByteArray, iv: ByteArray, alias: String): String? { + return String(data) // LALAL + } + + override fun encryptText(text: String, alias: String): Pair { + return text.toByteArray() to text.toByteArray() // LALAL + } + + override fun deleteKey(alias: String) { + // LALAL + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt new file mode 100644 index 0000000000..990cff580f --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -0,0 +1,42 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.* +import chat.simplex.common.DesktopApp +import chat.simplex.common.simplexWindowState +import java.io.* +import java.net.URI + +private fun applicationParentPath(): String = try { + DesktopApp::class.java.protectionDomain!!.codeSource.location.toURI().path + .replaceAfterLast("/", "") + .replaceAfterLast(File.separator, "") + .replace("/", File.separator) +} catch (e: Exception) { + "./" +} + +actual val dataDir: File = File(desktopPlatform.configPath) +actual val tmpDir: File = File(System.getProperty("java.io.tmpdir")) +actual val cacheDir: File = tmpDir + +@Composable +actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher = + remember { FileChooserLauncher(getContent, onResult) } + +actual class FileChooserLauncher actual constructor() { + var getContent: Boolean = false + lateinit var onResult: (URI?) -> Unit + + constructor(getContent: Boolean, onResult: (URI?) -> Unit): this() { + this.getContent = getContent + this.onResult = onResult + } + + actual suspend fun launch(input: String) { + val res = if (getContent) simplexWindowState.openDialog.awaitResult() else simplexWindowState.saveDialog.awaitResult() + onResult(if (!getContent && input.isNotEmpty() && res != null) File(res, input).toURI() else res?.toURI()) + } +} + +actual fun URI.inputStream(): InputStream? = File(URI("file:" + toString().removePrefix("file:"))).inputStream() +actual fun URI.outputStream(): OutputStream = File(URI("file:" + toString().removePrefix("file:"))).outputStream() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt new file mode 100644 index 0000000000..2d6011c223 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -0,0 +1,129 @@ +package chat.simplex.common.platform + +import androidx.compose.ui.graphics.* +import boofcv.io.image.ConvertBufferedImage +import boofcv.struct.image.GrayU8 +import chat.simplex.res.MR +import org.jetbrains.skia.Image +import java.awt.RenderingHints +import java.awt.image.BufferedImage +import java.io.* +import java.net.URI +import java.util.* +import javax.imageio.ImageIO +import kotlin.math.sqrt + +private fun errorBitmap(): ImageBitmap = + ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="))).toComposeImageBitmap() + +actual fun base64ToBitmap(base64ImageString: String): ImageBitmap { + val imageString = base64ImageString + .removePrefix("data:image/png;base64,") + .removePrefix("data:image/jpg;base64,") + return try { + ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode(imageString))).toComposeImageBitmap() + } catch (e: IOException) { + Log.e(TAG, "base64ToBitmap error: $e") + errorBitmap() + } +} + +actual fun resizeImageToStrSize(image: ImageBitmap, maxDataSize: Long): String { + var img = image + var str = compressImageStr(img) + while (str.length > maxDataSize) { + val ratio = sqrt(str.length.toDouble() / maxDataSize.toDouble()) + val clippedRatio = kotlin.math.min(ratio, 2.0) + val width = (img.width.toDouble() / clippedRatio).toInt() + val height = img.height * width / img.width + img = img.scale(width, height) + str = compressImageStr(img) + } + return str +} +actual fun resizeImageToDataSize(image: ImageBitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream = TODO() +actual fun cropToSquare(image: ImageBitmap): ImageBitmap { + var xOffset = 0 + var yOffset = 0 + val side = kotlin.math.min(image.height, image.width) + if (image.height < image.width) { + xOffset = (image.width - side) / 2 + } else { + yOffset = (image.height - side) / 2 + } + // LALAL MAKE REAL CROP + return image +} + +actual fun compressImageStr(bitmap: ImageBitmap): String { + val usePng = bitmap.hasAlpha + val ext = if (usePng) "png" else "jpg" + return try { + val encoded = Base64.getEncoder().encodeToString(compressImageData(bitmap, usePng).toByteArray()) + "data:image/$ext;base64,$encoded" + } catch (e: IOException) { + Log.e(TAG, "resizeImageToStrSize error: $e") + throw e + } +} + +actual fun compressImageData(bitmap: ImageBitmap, usePng: Boolean): ByteArrayOutputStream { + val stream = ByteArrayOutputStream() + stream.use { s -> ImageIO.write(bitmap.toAwtImage(), if (usePng) "png" else "jpg", s) } + // MAKE REAL COMPRESSION + //bitmap.compress(if (!usePng) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG, 85, stream) + return stream +} + +actual fun GrayU8.toImageBitmap(): ImageBitmap = ConvertBufferedImage.extractBuffered(this).toComposeImageBitmap() + +actual fun ImageBitmap.addLogo(): ImageBitmap { + val radius = (width * 0.16f).toInt() + val logoSize = (width * 0.24).toInt() + val logo: BufferedImage = MR.images.icon_foreground_common.image + val original = toAwtImage() + val withLogo = BufferedImage(width, height, original.type) + val g = withLogo.createGraphics() + g.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR + ) + g.drawImage(original, 0, 0, width, height, 0, 0, original.width, original.height, null) + g.fillRoundRect(width / 2 - radius / 2, height / 2 - radius / 2, radius, radius, radius, radius) + g.drawImage(logo, (width - logoSize) / 2, (height - logoSize) / 2, logoSize, logoSize, null) + g.dispose() + + return withLogo.toComposeImageBitmap() +} + +actual fun ImageBitmap.scale(width: Int, height: Int): ImageBitmap { + val original = toAwtImage() + val resized = BufferedImage(width, height, original.type) + val g = resized.createGraphics() + g.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR + ) + g.drawImage(original, 0, 0, width, height, 0, 0, original.width, original.height, null) + g.dispose() + return resized.toComposeImageBitmap() +} + +// LALAL +actual fun isImage(uri: URI): Boolean { + val path = uri.path.lowercase() + return path.endsWith(".gif") || + path.endsWith(".webp") || + path.endsWith(".png") || + path.endsWith(".jpg") || + path.endsWith(".jpeg") +} + +actual fun isAnimImage(uri: URI, drawable: Any?): Boolean { + val path = uri.path.lowercase() + return path.endsWith(".gif") || path.endsWith(".webp") +} + +@Suppress("NewApi") +actual fun loadImageBitmap(inputStream: InputStream): ImageBitmap = + Image.makeFromEncoded(inputStream.readAllBytes()).toComposeImageBitmap() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Log.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Log.desktop.kt new file mode 100644 index 0000000000..395754c51b --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Log.desktop.kt @@ -0,0 +1,8 @@ +package chat.simplex.common.platform + +actual object Log { + actual fun d(tag: String, text: String) = println("D: $text") + actual fun e(tag: String, text: String) = println("E: $text") + actual fun i(tag: String, text: String) = println("I: $text") + actual fun w(tag: String, text: String) = println("W: $text") +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt new file mode 100644 index 0000000000..72e163c2ed --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt @@ -0,0 +1,15 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +actual fun Modifier.navigationBarsWithImePadding(): Modifier = this + +@Composable +actual fun ProvideWindowInsets( + consumeWindowInsets: Boolean, + windowInsetsAnimationsEnabled: Boolean, + content: @Composable () -> Unit +) { + content() +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Notifications.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Notifications.desktop.kt new file mode 100644 index 0000000000..40f89d17f1 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Notifications.desktop.kt @@ -0,0 +1,5 @@ +package chat.simplex.common.platform + +import chat.simplex.common.model.NotificationsMode + +actual fun allowedToShowNotification(): Boolean = true diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt new file mode 100644 index 0000000000..13dd657820 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt @@ -0,0 +1,28 @@ +package chat.simplex.common.platform + +import java.io.File +import java.util.* + +private val home = System.getProperty("user.home") +val desktopPlatform = detectDesktopPlatform() + +enum class DesktopPlatform(val libPath: String, val libExtension: String, val configPath: String) { + LINUX_X86_64("/libs/linux-x86_64", "so", "$home/.config/simplex"), + LINUX_AARCH64("/libs/aarch64", "so", "$home/.config/simplex"), + WINDOWS_X86_64("/libs/windows-x86_64", "dll", System.getenv("AppData") + File.separator + "simplex"), + MAC_X86_64("/libs/mac-x86_64", "dylib", "$home/.config/simplex"), + MAC_AARCH64("/libs/mac-aarch64", "dylib", "$home/.config/simplex"); +} + +private fun detectDesktopPlatform(): DesktopPlatform { + val os = System.getProperty("os.name", "generic").lowercase(Locale.ENGLISH) + val arch = System.getProperty("os.arch") + return when { + os == "linux" && (arch.contains("x86") || arch == "amd64") -> DesktopPlatform.LINUX_X86_64 + os == "linux" && arch == "aarch64" -> DesktopPlatform.LINUX_AARCH64 + os.contains("windows") && (arch.contains("x86") || arch == "amd64") -> DesktopPlatform.WINDOWS_X86_64 + os.contains("mac") && arch.contains("x86") -> DesktopPlatform.MAC_X86_64 + os.contains("mac") && arch.contains("aarch64") -> DesktopPlatform.MAC_AARCH64 + else -> TODO("Currently, your processor's architecture ($arch) or os ($os) are unsupported. Please, contact us") + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt new file mode 100644 index 0000000000..7148f8217e --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt @@ -0,0 +1,107 @@ +package chat.simplex.common.platform + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.* +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource +import kotlinx.coroutines.delay +import kotlin.math.min + +@Composable +actual fun PlatformTextField( + composeState: MutableState, + textStyle: MutableState, + showDeleteTextButton: MutableState, + userIsObserver: Boolean, + onMessageChange: (String) -> Unit +) { + val cs = composeState.value + val focusRequester = remember { FocusRequester() } + val keyboard = LocalSoftwareKeyboardController.current + val padding = PaddingValues(12.dp, 7.dp, 45.dp, 0.dp) + LaunchedEffect(cs.contextItem) { + if (cs.contextItem !is ComposeContextItem.QuotedItem) return@LaunchedEffect + // In replying state + focusRequester.requestFocus() + delay(50) + keyboard?.show() + } + val isRtl = remember(cs.message) { isRtl(cs.message.subSequence(0, min(50, cs.message.length))) } + BasicTextField( + value = cs.message, + onValueChange = { + if (!composeState.value.inProgress && !(composeState.value.preview is ComposePreview.VoicePreview && it != "")) { + onMessageChange(it) + } + }, + textStyle = textStyle.value, + maxLines = 16, + keyboardOptions = KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences, + autoCorrect = true + ), + modifier = Modifier.padding(vertical = 4.dp).focusRequester(focusRequester), + cursorBrush = SolidColor(MaterialTheme.colors.secondary), + decorationBox = { innerTextField -> + Surface( + shape = RoundedCornerShape(18.dp), + border = BorderStroke(1.dp, MaterialTheme.colors.secondary) + ) { + Row( + Modifier.background(MaterialTheme.colors.background), + verticalAlignment = Alignment.Bottom + ) { + CompositionLocalProvider( + LocalLayoutDirection provides if (isRtl) LayoutDirection.Rtl else LocalLayoutDirection.current + ) { + Box( + Modifier + .weight(1f) + .padding(horizontal = 12.dp) + .padding(top = 4.dp) + .padding(bottom = 6.dp) + ) { + innerTextField() + } + } + } + } + } + ) + showDeleteTextButton.value = cs.message.split("\n").size >= 4 && !cs.inProgress + if (composeState.value.preview is ComposePreview.VoicePreview) { + ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding) + } else if (userIsObserver) { + ComposeOverlay(MR.strings.you_are_observer, textStyle, padding) + } +} + +@Composable +private fun ComposeOverlay(textId: StringResource, textStyle: MutableState, padding: PaddingValues) { + Text( + generalGetString(textId), + Modifier.padding(padding), + color = MaterialTheme.colors.secondary, + style = textStyle.value.copy(fontStyle = FontStyle.Italic) + ) +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt new file mode 100644 index 0000000000..7e2ab9d5d0 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt @@ -0,0 +1,53 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.MutableState +import chat.simplex.common.model.ChatItem +import kotlinx.coroutines.CoroutineScope + +actual class RecorderNative: RecorderInterface { + override fun start(onProgressUpdate: (position: Int?, finished: Boolean) -> Unit): String { + /*LALAL*/ + return "" + } + + override fun stop(): Int { + /*LALAL*/ + return 0 + } +} + +actual object AudioPlayer: AudioPlayerInterface { + override fun play(filePath: String?, audioPlaying: MutableState, progress: MutableState, duration: MutableState, resetOnEnd: Boolean) { + /*LALAL*/ + } + + override fun stop() { + /*LALAL*/ + } + + override fun stop(item: ChatItem) { + /*LALAL*/ + } + + override fun stop(fileName: String?) { + TODO("Not yet implemented") + } + + override fun pause(audioPlaying: MutableState, pro: MutableState) { + TODO("Not yet implemented") + } + + override fun seekTo(ms: Int, pro: MutableState, filePath: String?) { + /*LALAL*/ + } + + override fun duration(filePath: String): Int? { + /*LALAL*/ + return null + } +} + +actual object SoundPlayer: SoundPlayerInterface { + override fun start(scope: CoroutineScope, sound: Boolean) { /*LALAL*/ } + override fun stop() { /*LALAL*/ } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt new file mode 100644 index 0000000000..1bedcb6f44 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt @@ -0,0 +1,58 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.* +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.russhwolf.settings.* +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.desc.desc +import java.io.File +import java.util.* + +@Composable +actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font = + androidx.compose.ui.text.platform.Font("MR/fonts/$res.ttf", weight, style) + +actual fun StringResource.localized(): String = desc().toString() + +actual fun isInNightMode() = false + +private val settingsFile = + File(desktopPlatform.configPath + File.separator + "settings.properties") + .also { it.parentFile.mkdirs() } +private val settingsThemesFile = + File(desktopPlatform.configPath + File.separator + "themes.properties") + .also { it.parentFile.mkdirs() } +private val settingsProps = + Properties() + .also { try { it.load(settingsFile.reader()) } catch (e: Exception) { Properties() } } +private val settingsThemesProps = + Properties() + .also { try { it.load(settingsThemesFile.reader()) } catch (e: Exception) { Properties() } } + +actual val settings: Settings = PropertiesSettings(settingsProps) { settingsProps.store(settingsFile.writer(), "") } +actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) { settingsThemesProps.store(settingsThemesFile.writer(), "") } + +actual fun screenOrientation(): ScreenOrientation = ScreenOrientation.UNDEFINED + +@Composable // LALAL +actual fun screenWidth(): Dp { + return java.awt.Toolkit.getDefaultToolkit().screenSize.width.dp.also { println("LALAL $it") } + /*var width by remember { mutableStateOf(java.awt.Toolkit.getDefaultToolkit().screenSize.width.also { println("LALAL $it") }) } + SideEffect { + if (width != java.awt.Toolkit.getDefaultToolkit().screenSize.width) + width = java.awt.Toolkit.getDefaultToolkit().screenSize.width + } + return width*/ +}// LALAL java.awt.Desktop.getDesktop() + +actual fun isRtl(text: CharSequence): Boolean { + if (text.isEmpty()) return false + return text.any { char -> + val dir = Character.getDirectionality(char) + dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt new file mode 100644 index 0000000000..84e24a1d55 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt @@ -0,0 +1,31 @@ +package chat.simplex.common.platform + +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.UriHandler +import androidx.compose.ui.text.AnnotatedString +import chat.simplex.common.views.helpers.withApi +import java.io.File +import java.net.URI +import java.net.URLEncoder +import chat.simplex.res.MR + +actual fun UriHandler.sendEmail(subject: String, body: CharSequence) { + val subjectEncoded = URLEncoder.encode(subject, "UTF-8").replace("+", "%20") + val bodyEncoded = URLEncoder.encode(subject, "UTF-8").replace("+", "%20") + openUri("mailto:?subject=$subjectEncoded&body=$bodyEncoded") +} + +actual fun ClipboardManager.shareText(text: String) { + setText(AnnotatedString(text)) + showToast(MR.strings.copied.localized()) +} + +actual fun shareFile(text: String, filePath: String) { + withApi { + FileChooserLauncher(false) { to: URI? -> + if (to != null) { + copyFileToFile(File(filePath), to) {} + } + }.launch(filePath) + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/UI.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/UI.desktop.kt new file mode 100644 index 0000000000..1ea5018fcc --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/UI.desktop.kt @@ -0,0 +1,19 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.* +import chat.simplex.common.simplexWindowState +import chat.simplex.common.views.helpers.KeyboardState + +actual fun showToast(text: String, timeout: Long) { + simplexWindowState.toasts.add(text to timeout) +} + +@Composable +actual fun LockToCurrentOrientationUntilDispose() {} + +@Composable +actual fun LocalMultiplatformView(): Any? = null + +@Composable +actual fun getKeyboardState(): State = remember { mutableStateOf(KeyboardState.Opened) } +actual fun hideKeyboard(view: Any?) {} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt new file mode 100644 index 0000000000..0c40c1c350 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt @@ -0,0 +1,50 @@ +package chat.simplex.common.platform + +import androidx.compose.runtime.MutableState +import androidx.compose.ui.graphics.ImageBitmap +import java.net.URI + +actual class VideoPlayer: VideoPlayerInterface { + actual companion object { + actual fun getOrCreate( + uri: URI, + gallery: Boolean, + defaultPreview: ImageBitmap, + defaultDuration: Long, + soundEnabled: Boolean + ): VideoPlayer = VideoPlayer() + actual fun enableSound(enable: Boolean, fileName: String?, gallery: Boolean): Boolean { TODO() } + actual fun release(uri: URI, gallery: Boolean, remove: Boolean) { TODO() } + actual fun stopAll() { /*LALAL*/ } + actual fun releaseAll() { /*LALAL*/ } + } + + override val soundEnabled: MutableState + get() = TODO("Not yet implemented") + override val brokenVideo: MutableState + get() = TODO("Not yet implemented") + override val videoPlaying: MutableState + get() = TODO("Not yet implemented") + override val progress: MutableState + get() = TODO("Not yet implemented") + override val duration: MutableState + get() = TODO("Not yet implemented") + override val preview: MutableState + get() = TODO("Not yet implemented") + + override fun stop() { + TODO("Not yet implemented") + } + + override fun play(resetOnEnd: Boolean) { + TODO("Not yet implemented") + } + + override fun enableSound(enable: Boolean): Boolean { + TODO("Not yet implemented") + } + + override fun release(remove: Boolean) { + TODO("Not yet implemented") + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Type.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Type.desktop.kt new file mode 100644 index 0000000000..9026752366 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Type.desktop.kt @@ -0,0 +1,14 @@ +package chat.simplex.common.ui.theme + +import androidx.compose.ui.text.font.* +import androidx.compose.ui.text.platform.Font +import chat.simplex.res.MR + +actual val Inter: FontFamily = FontFamily( + Font(MR.fonts.Inter.regular.file), + Font(MR.fonts.Inter.italic.file, style = FontStyle.Italic), + Font(MR.fonts.Inter.bold.file, FontWeight.Bold), + Font(MR.fonts.Inter.semibold.file, FontWeight.SemiBold), + Font(MR.fonts.Inter.medium.file, FontWeight.Medium), + Font(MR.fonts.Inter.light.file, FontWeight.Light) +) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt new file mode 100644 index 0000000000..df45a8acb0 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt @@ -0,0 +1,8 @@ +package chat.simplex.common.views.call + +import androidx.compose.runtime.Composable + +@Composable +actual fun ActiveCallView() { + // LALAL +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt new file mode 100644 index 0000000000..c7eeef3413 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt @@ -0,0 +1,24 @@ +package chat.simplex.common.views.chat + +import androidx.compose.runtime.* +import chat.simplex.common.views.helpers.AttachmentOption +import java.net.URI + +@Composable +actual fun AttachmentSelection( + composeState: MutableState, + attachmentOption: MutableState, + processPickedFile: (URI?, String?) -> Unit, + processPickedMedia: (List, String?) -> Unit +) { + LaunchedEffect(attachmentOption.value) { + when (attachmentOption.value) { + AttachmentOption.CameraPhoto -> {} + AttachmentOption.GalleryImage -> {} + AttachmentOption.GalleryVideo -> {} + AttachmentOption.File -> {} + else -> {} + } + attachmentOption.value = null + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.desktop.kt new file mode 100644 index 0000000000..7ea2ef5368 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.desktop.kt @@ -0,0 +1,8 @@ +package chat.simplex.common.views.chat + +import androidx.compose.runtime.Composable + +@Composable +actual fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { + ScanCodeLayout(verifyCode, close) +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/SendMsgView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/SendMsgView.desktop.kt new file mode 100644 index 0000000000..05ff1d73dc --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/SendMsgView.desktop.kt @@ -0,0 +1,11 @@ +package chat.simplex.common.views.chat + +import androidx.compose.runtime.Composable + +@Composable +actual fun allowedToRecordVoiceByPlatform(): Boolean = true + +@Composable +actual fun VoiceButtonWithoutPermissionByPlatform() { + VoiceButtonWithoutPermission {} +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt new file mode 100644 index 0000000000..a9c7f5f0ae --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt @@ -0,0 +1,28 @@ +package chat.simplex.common.views.chat.item + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.Painter +import chat.simplex.common.model.CIFile +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.ModalManager +import java.net.URI + +@Composable +actual fun SimpleAndAnimatedImageView( + uri: URI, + imageBitmap: ImageBitmap, + file: CIFile?, + imageProvider: () -> ImageGalleryProvider, + ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit +) { + // LALAL make it animated too + ImageView(imageBitmap.toAwtImage().toPainter()) { + if (getLoadedFilePath(file) != null) { + ModalManager.shared.showCustomModal(animated = false) { close -> + ImageFullScreenView(imageProvider, close) + } + } + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt new file mode 100644 index 0000000000..aac995e480 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt @@ -0,0 +1,23 @@ +package chat.simplex.common.views.chat.item + +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import chat.simplex.common.platform.VideoPlayer + +@Composable +actual fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: () -> Unit, stop: () -> Unit) { + /* LALAL */ +} + +@Composable +actual fun LocalWindowWidth(): Dp { + return with(LocalDensity.current) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() } + /*val density = LocalDensity.current + var width by remember { mutableStateOf(with(density) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() }) } + SideEffect { + if (width != with(density) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() }) + width = with(density) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() } + } + return width.also { println("LALAL $it") }*/ +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt new file mode 100644 index 0000000000..6f4878cfe3 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt @@ -0,0 +1,23 @@ +package chat.simplex.common.views.chat.item + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import chat.simplex.common.model.ChatItem +import chat.simplex.common.model.MsgContent +import chat.simplex.common.platform.FileChooserLauncher +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserLauncher, showMenu: MutableState) { + ItemAction(stringResource(MR.strings.save_verb), painterResource(MR.images.ic_download), onClick = { + when (cItem.content.msgContent) { + is MsgContent.MCImage -> saveImage(getAppFileUri(cItem.file?.fileName ?: return@ItemAction)) + is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") } + else -> {} + } + showMenu.value = false + }) +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt new file mode 100644 index 0000000000..e4e483092d --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt @@ -0,0 +1,28 @@ +package chat.simplex.common.views.chat.item + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.layout.ContentScale +import chat.simplex.common.platform.VideoPlayer +import chat.simplex.common.views.helpers.getBitmapFromUri +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import java.net.URI + +@Composable +actual fun FullScreenImageView(modifier: Modifier, uri: URI, imageBitmap: ImageBitmap) { + Image( + getBitmapFromUri(uri, false) ?: MR.images.decentralized.image.toComposeImageBitmap(), + contentDescription = stringResource(MR.strings.image_descr), + contentScale = ContentScale.Fit, + modifier = modifier, + ) +} +@Composable +actual fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier) { + +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt new file mode 100644 index 0000000000..ade674dff2 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt @@ -0,0 +1,119 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.runtime.* +import androidx.compose.ui.input.key.* +import androidx.compose.ui.window.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.awt.FileDialog +import java.io.File +import java.util.* +import javax.swing.JFileChooser +import javax.swing.filechooser.FileFilter +import javax.swing.filechooser.FileNameExtensionFilter + +@Composable +actual fun DefaultDialog( + onDismissRequest: () -> Unit, + content: @Composable () -> Unit +) { + Dialog( + undecorated = true, + title = "", + onCloseRequest = onDismissRequest, + onPreviewKeyEvent = { event -> + if (event.key == Key.Escape && event.type == KeyEventType.KeyUp) { + onDismissRequest(); true + } else false + } + ) { + content() + } +} + +@Composable +fun FrameWindowScope.FileDialogChooser( + title: String, + isLoad: Boolean, + extensions: List = emptyList(), + onResult: (result: File?) -> Unit +) { + if (isLinux()) { + FileDialogChooserMultiple(title, isLoad, extensions) { onResult(it.firstOrNull()) } + } else { + FileDialogAwt(title, isLoad, onResult) + } +} + +@Composable +fun FrameWindowScope.FileDialogChooserMultiple( + title: String, + isLoad: Boolean, + extensions: List = emptyList(), + onResult: (result: List) -> Unit +) { + val scope = rememberCoroutineScope() + DisposableEffect(Unit) { + val job = scope.launch(Dispatchers.Main) { + val fileChooser = JFileChooser() + fileChooser.dialogTitle = title + fileChooser.isMultiSelectionEnabled = isLoad + fileChooser.isAcceptAllFileFilterUsed = extensions.isEmpty() + extensions.forEach { fileChooser.addChoosableFileFilter(it) } + val returned = if (isLoad) { + fileChooser.showOpenDialog(window) + } else { + fileChooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY + fileChooser.showSaveDialog(window) + } + val result = when (returned) { + JFileChooser.APPROVE_OPTION -> { + if (isLoad) { + fileChooser.selectedFiles.filter { it.canRead() } + } else { + if (!fileChooser.fileFilter.accept(fileChooser.selectedFile)) { + val ext = (fileChooser.fileFilter as FileNameExtensionFilter).extensions[0] + fileChooser.selectedFile = File(fileChooser.selectedFile.absolutePath + ".$ext") + } + listOf(fileChooser.selectedFile) + } + } + else -> listOf(); + } + onResult(result) + } + onDispose { + job.cancel() + } + } +} + +/* +* Has graphic glitches on many Linux distributions, so use only on non-Linux systems +* */ +@Composable +private fun FrameWindowScope.FileDialogAwt( + title: String, + isLoad: Boolean, + onResult: (result: File?) -> Unit +) = AwtWindow( + create = { + object: FileDialog(window, "Choose a file", if (isLoad) LOAD else SAVE) { + override fun setVisible(value: Boolean) { + super.setVisible(value) + if (value) { + if (file != null) { + onResult(File(directory).resolve(file)) + } else { + onResult(null) + } + } + } + }.apply { + this.title = title + } + }, + dispose = FileDialog::dispose +) + +fun isLinux(): Boolean = System.getProperty("os.name", "generic").lowercase(Locale.ENGLISH) == "linux" diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt new file mode 100644 index 0000000000..e47fa4ae8b --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt @@ -0,0 +1,34 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier + +actual interface DefaultExposedDropdownMenuBoxScope { + @Composable + actual fun DefaultExposedDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier, + content: @Composable ColumnScope.() -> Unit + ) { + Column { + content() + } + } +} + +@Composable +actual fun DefaultExposedDropdownMenuBox( + expanded: Boolean, + onExpandedChange: (Boolean) -> Unit, + modifier: Modifier, + content: @Composable DefaultExposedDropdownMenuBoxScope.() -> Unit +) { + if (expanded) { + val obj = remember { object : DefaultExposedDropdownMenuBoxScope {} } + obj.content() + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt new file mode 100644 index 0000000000..d824627449 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt @@ -0,0 +1,57 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.ImageBitmap +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.dp +import chat.simplex.common.platform.rememberFileChooserLauncher +import chat.simplex.res.MR +import chat.simplex.common.views.newchat.ActionButton +import java.net.URI + +@Composable +actual fun GetImageBottomSheet( + imageBitmap: MutableState, + onImageChange: (ImageBitmap) -> Unit, + hideBottomSheet: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .onFocusChanged { focusState -> + if (!focusState.hasFocus) hideBottomSheet() + } + ) { + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 30.dp), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + val processPickedImage = { uri: URI? -> + if (uri != null) { + val bitmap = getBitmapFromUri(uri) + if (bitmap != null) { + imageBitmap.value = uri + onImageChange(bitmap) + } + hideBottomSheet() + } + } + val pickImageLauncher = rememberFileChooserLauncher(true, processPickedImage) + // LALAL + /*ActionButton(null, stringResource(MR.strings.use_camera_button), icon = painterResource(MR.images.ic_photo_camera)) { + hideBottomSheet() + }*/ + ActionButton(null, stringResource(MR.strings.from_gallery_button), icon = painterResource(MR.images.ic_image)) { + // LALAL support providing file extensions + withApi { pickImageLauncher.launch("") } + } + } + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt new file mode 100644 index 0000000000..a251b7dc20 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt @@ -0,0 +1,16 @@ +package chat.simplex.common.views.helpers + +import chat.simplex.common.views.usersettings.LAMode + +actual fun authenticate( + promptTitle: String, + promptSubtitle: String, + selfDestruct: Boolean, + usingLAMode: LAMode, + completed: (LAResult) -> Unit +) { + when (usingLAMode) { + LAMode.PASSCODE -> authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, completed) + else -> {} + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt new file mode 100644 index 0000000000..36aef2437e --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt @@ -0,0 +1,72 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.ui.graphics.* +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.Density +import chat.simplex.common.model.CIFile +import chat.simplex.common.platform.* +import chat.simplex.common.simplexWindowState +import java.io.File +import java.net.URI +import java.util.* +import javax.imageio.ImageIO +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.io.path.toPath + +// LALAL MAKE REALLY ANNOTATED STRING FROM HTML +actual fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString { + return AnnotatedString(text) +} + +actual fun getAppFileUri(fileName: String): URI = + URI("file:" + getAppFilesDirectory() + File.separator + fileName) + +actual fun getLoadedImage(file: CIFile?): ImageBitmap? { + val filePath = getLoadedFilePath(file) + return if (filePath != null) { + val uri = getAppFileUri(filePath.substringAfterLast(File.separator)) + getBitmapFromUri(uri, false) + } else { + null + } +} + +actual fun getFileName(uri: URI): String? = uri.toPath().toFile().name + +actual fun getAppFilePath(uri: URI): String? = getFilesDirectory() + File.separator + "app_files" + +actual fun getFileSize(uri: URI): Long? = uri.toPath().toFile().length() + +actual fun getBitmapFromUri(uri: URI, withAlertOnException: Boolean): ImageBitmap? = + ImageIO.read(uri.inputStream()).toComposeImageBitmap() + +// LALAL implement to support animated drawable +actual fun getDrawableFromUri(uri: URI, withAlertOnException: Boolean): Any? = null + +actual suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean): File? { + val file = simplexWindowState.saveDialog.awaitResult() + return if (file != null) { + try { + val ext = if (asPng) "png" else "jpg" + val newFile = File(file.absolutePath + File.separator + generateNewFileName("IMG", ext)) + // LALAL FILE IS EMPTY + ImageIO.write(image.toAwtImage(), ext.uppercase(), newFile.outputStream()) + newFile + } catch (e: Exception) { + Log.e(TAG, "Util.kt saveTempImageUncompressed error: ${e.message}") + null + } + } else null +} + +actual fun getBitmapFromVideo(uri: URI, timestamp: Long?, random: Boolean): VideoPlayerInterface.PreviewAndDuration { + // LALAL + return VideoPlayerInterface.PreviewAndDuration(preview = null, timestamp = 0L, duration = 0L) +} + +@OptIn(ExperimentalEncodingApi::class) +actual fun ByteArray.toBase64StringForPassphrase(): String = Base64.encode(this) + +@OptIn(ExperimentalEncodingApi::class) +actual fun String.toByteArrayFromBase64ForPassphrase(): ByteArray = Base64.decode(this) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCode.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCode.desktop.kt new file mode 100644 index 0000000000..ae5cd24ce7 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCode.desktop.kt @@ -0,0 +1,18 @@ +package chat.simplex.common.views.newchat + +import androidx.compose.ui.graphics.* + +actual fun ImageBitmap.replaceColor(from: Int, to: Int): ImageBitmap { + val image = toAwtImage() + val pixels = IntArray(width * height) + image.getRGB(0, 0, width, height, pixels, 0, image.width) + var i = 0 + while (i < pixels.size) { + if (pixels[i] == from) { + pixels[i] = to + } + i++ + } + image.setRGB(0, 0, width, height, pixels, 0, image.width) + return image.toComposeImageBitmap() +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt new file mode 100644 index 0000000000..16d35b5b8d --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt @@ -0,0 +1,8 @@ +package chat.simplex.common.views.newchat + +import androidx.compose.runtime.* + +@Composable +actual fun QRCodeScanner(onBarcode: (String) -> Unit) { + //LALAL +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt new file mode 100644 index 0000000000..66f2d5c6d2 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt @@ -0,0 +1,12 @@ +package chat.simplex.common.views.newchat + +import androidx.compose.runtime.Composable +import chat.simplex.common.model.ChatModel + +@Composable +actual fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { + ConnectContactLayout( + chatModelIncognito = chatModel.incognito.value, + close = close + ) +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.desktop.kt new file mode 100644 index 0000000000..f770de904a --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.desktop.kt @@ -0,0 +1,6 @@ +package chat.simplex.common.views.onboarding + +import androidx.compose.runtime.Composable + +@Composable +actual fun SetNotificationsModeAdditions() {} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt new file mode 100644 index 0000000000..851cf137dd --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -0,0 +1,70 @@ +package chat.simplex.common.views.usersettings + +import SectionBottomSpacer +import SectionDividerSpaced +import SectionView +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.SharedPreference +import chat.simplex.common.platform.defaultLocale +import chat.simplex.common.ui.theme.ThemeColor +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.delay +import java.util.Locale + +@Composable +actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { + AppearanceScope.AppearanceLayout( + m.controller.appPrefs.appLanguage, + m.controller.appPrefs.systemDarkTheme, + showSettingsModal = showSettingsModal, + editColor = { name, initialColor -> + ModalManager.shared.showModalCloseable { close -> + ColorEditor(name, initialColor, close) + } + }, + ) +} + +@Composable +fun AppearanceScope.AppearanceLayout( + languagePref: SharedPreference, + systemDarkTheme: SharedPreference, + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + editColor: (ThemeColor, Color) -> Unit, +) { + Column( + Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ) { + AppBarTitle(stringResource(MR.strings.appearance_settings)) + SectionView(stringResource(MR.strings.settings_section_title_language), padding = PaddingValues()) { + val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } + LangSelector(state) { + state.value = it + withApi { + delay(200) + if (it == "system") { + languagePref.set(null) + Locale.setDefault(defaultLocale) + } else { + languagePref.set(it) + Locale.setDefault(Locale.forLanguageTag(it)) + } + } + } + } + SectionDividerSpaced(maxTopPadding = true) + ThemesSection(systemDarkTheme, showSettingsModal, editColor) + SectionBottomSpacer() + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.desktop.kt new file mode 100644 index 0000000000..a51fd20c5b --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.desktop.kt @@ -0,0 +1,17 @@ +package chat.simplex.common.views.usersettings + +import SectionView +import androidx.compose.runtime.Composable +import chat.simplex.common.model.ChatModel +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun PrivacyDeviceSection( + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + setPerformLA: (Boolean) -> Unit, +) { + SectionView(stringResource(MR.strings.settings_section_title_device)) { + ChatLockItem(showSettingsModal, setPerformLA) + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt new file mode 100644 index 0000000000..464c286316 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt @@ -0,0 +1,9 @@ +package chat.simplex.common.views.usersettings + +import androidx.compose.runtime.Composable +import chat.simplex.common.model.ServerCfg + +@Composable +actual fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) { + ScanProtocolServerLayout(onNext) +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt new file mode 100644 index 0000000000..95a079fe1b --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt @@ -0,0 +1,21 @@ +package chat.simplex.common.views.usersettings + +import SectionView +import androidx.compose.runtime.Composable +import chat.simplex.common.model.ChatModel +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun SettingsSectionApp( + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showVersion: () -> Unit, + withAuth: (title: String, desc: String, block: () -> Unit) -> Unit +) { + SectionView(stringResource(MR.strings.settings_section_title_app)) { + SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true) + AppVersionItem(showVersion) + } +} diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt new file mode 100644 index 0000000000..a004998e0d --- /dev/null +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt @@ -0,0 +1,8 @@ +import chat.simplex.common.platform.* +import chat.simplex.common.showApp + +fun main() { + initHaskell() + initApp() + return showApp() +} From 0bdd96ae8a4782f8162ee8cf04532c59d5144c49 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:02:38 +0300 Subject: [PATCH 03/18] multiplatform: scripts for building the lib (#2682) * multiplatform: scripts for building the lib * refactor mac script * fix path * changes in Linux script --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- scripts/desktop/build-lib-linux.sh | 25 +++++++++++ scripts/desktop/build-lib-mac.sh | 72 ++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100755 scripts/desktop/build-lib-linux.sh create mode 100755 scripts/desktop/build-lib-mac.sh diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh new file mode 100755 index 0000000000..0e19b5a925 --- /dev/null +++ b/scripts/desktop/build-lib-linux.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +OS=linux +ARCH=${1:-`uname -a | rev | cut -d' ' -f2 | rev`} +GHC_VERSION=8.10.7 + +BUILD_DIR=dist-newstyle/build/$ARCH-$OS/ghc-${GHC_VERSION}/simplex-chat-* + +rm -rf $BUILD_DIR +cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN' --ghc-options="-optl-L$(ghc --print-libdir)/rts -optl-Wl,--as-needed,-lHSrts_thr-ghc$GHC_VERSION" +cd $BUILD_DIR/build +#patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so +#patchelf --add-rpath '$ORIGIN' libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so +mkdir deps +ldd libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so | grep "ghc" | cut -d' ' -f 3 | xargs -I {} cp {} ./deps/ + +cd - + +rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +rm -rf apps/multiplatform/common/src/commonMain/resources/libs/$OS-$ARCH/ +rm -rf apps/multiplatform/desktop/build/cmake + +mkdir -p apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +cp -r $BUILD_DIR/build/deps apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +cp $BUILD_DIR/build/libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh new file mode 100755 index 0000000000..59c0aeb127 --- /dev/null +++ b/scripts/desktop/build-lib-mac.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +OS=mac +ARCH="${1:-`uname -a | rev | cut -d' ' -f1 | rev`}" +if [ "$ARCH" == "arm64" ]; then + ARCH=aarch64 +fi +LIB_EXT=dylib +LIB=libHSsimplex-chat-*-inplace-ghc*.$LIB_EXT +GHC_LIBS_DIR=$(ghc --print-libdir) + +BUILD_DIR=dist-newstyle/build/$ARCH-*/ghc-*/simplex-chat-* + +rm -rf $BUILD_DIR +cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/rts -optl-lHSrts_thr-ghc8.10.7 -optl-lffi" + +cd $BUILD_DIR/build +mkdir deps 2> /dev/null + +# It's not included by default for some reason. Compiled lib tries to find system one but it's not always available +cp $GHC_LIBS_DIR/rts/libffi.dylib ./deps + +DYLIBS=`otool -L $LIB | grep @rpath | tail -n +2 | cut -d' ' -f 1 | cut -d'/' -f2` +RPATHS=`otool -l $LIB | grep "path "| cut -d' ' -f11` + +PROCESSED_LIBS=() + +function copy_deps() { + local LIB=$1 + if [[ "${PROCESSED_LIBS[*]}" =~ "$LIB" ]]; then + return 0 + fi + + PROCESSED_LIBS+=$LIB + local DYLIBS=`otool -L $LIB | grep @rpath | tail -n +2 | cut -d' ' -f 1 | cut -d'/' -f2` + local NON_FINAL_RPATHS=`otool -l $LIB | grep "path "| cut -d' ' -f11` + local RPATHS=`otool -l $LIB | grep "path "| cut -d' ' -f11 | sed "s|@loader_path/..|$GHC_LIBS_DIR|"` + + cp $LIB ./deps + if [[ "$NON_FINAL_RPATHS" == *"@loader_path/.."* ]]; then + # Need to point the lib to @loader_path instead + install_name_tool -add_rpath @loader_path ./deps/`basename $LIB` + fi + #echo LIB $LIB + #echo DYLIBS ${DYLIBS[@]} + #echo RPATHS ${RPATHS[@]} + + for DYLIB in $DYLIBS; do + for RPATH in $RPATHS; do + if [ -f "$RPATH/$DYLIB" ]; then + #echo DEP IS "$RPATH/$DYLIB" + if [ ! -f "deps/$DYLIB" ]; then + cp "$RPATH/$DYLIB" ./deps + fi + copy_deps "$RPATH/$DYLIB" + fi + done + done +} + +copy_deps $LIB +rm deps/`basename $LIB` + +cd - + +rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +rm -rf apps/multiplatform/common/src/commonMain/resources/libs/$OS-$ARCH/ +rm -rf apps/multiplatform/desktop/build/cmake + +mkdir -p apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +cp -r $BUILD_DIR/build/deps apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +cp $BUILD_DIR/build/libHSsimplex-chat-*-inplace-ghc*.$LIB_EXT apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ From 8dd90733b88ab4acc06411de109390d022e168d3 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:00:37 +0300 Subject: [PATCH 04/18] multiplatform: default files and directories were changed (#2683) * multiplatform: default files and directories were changed * changes in paths and location of declaration * more renames * different paths * update linux paths --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../main/java/chat/simplex/app/SimplexApp.kt | 1 + .../simplex/common/platform/Files.android.kt | 8 ++++++- .../views/helpers/GetImageView.android.kt | 2 +- .../common/views/helpers/Utils.android.kt | 2 +- .../chat/simplex/common/model/SimpleXAPI.kt | 4 ++-- .../chat/simplex/common/platform/Core.kt | 3 +-- .../chat/simplex/common/platform/Files.kt | 23 +++++++------------ .../common/views/database/ChatArchiveView.kt | 2 +- .../views/database/DatabaseErrorView.kt | 10 ++++---- .../common/views/database/DatabaseView.kt | 18 +++++++-------- .../common/views/helpers/DatabaseUtils.kt | 3 +-- .../simplex/common/views/helpers/Utils.kt | 2 +- .../common/platform/AppCommon.desktop.kt | 8 +++---- .../simplex/common/platform/Files.desktop.kt | 12 +++++++--- .../common/platform/Platform.desktop.kt | 14 ++++++----- .../common/views/helpers/Utils.desktop.kt | 4 ++-- .../desktop/src/jvmMain/kotlin/Main.kt | 1 + 17 files changed, 61 insertions(+), 56 deletions(-) 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 af50e6b057..3174e06265 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 @@ -36,6 +36,7 @@ class SimplexApp: Application(), LifecycleEventObserver { initHaskell() initMultiplatform() tmpDir.deleteRecursively() + withBGApi { initChatController() runMigrations() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt index 522d861590..f809b82caa 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -13,7 +13,13 @@ import java.net.URI actual val dataDir: File = androidAppContext.dataDir actual val tmpDir: File = androidAppContext.getDir("temp", Application.MODE_PRIVATE) -actual val cacheDir: File = androidAppContext.cacheDir +actual val filesDir: File = File(dataDir.absolutePath + File.separator + "files") +actual val appFilesDir: File = File(filesDir.absolutePath + File.separator + "app_files") +actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files") +actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files" + +actual val chatDatabaseFileName: String = "files_chat.db" +actual val agentDatabaseFileName: String = "files_agent.db" @Composable actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt index 95dac842f2..92c0845154 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt @@ -43,7 +43,7 @@ val errorBitmap: Bitmap = BitmapFactory.decodeByteArray(errorBitmapBytes, 0, err class CustomTakePicturePreview(var uri: Uri?, var tmpFile: File?): ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Void?): Intent { - tmpFile = File.createTempFile("image", ".bmp", File(getAppFilesDirectory())) + tmpFile = File.createTempFile("image", ".bmp", appFilesDir) // Since the class should return Uri, the file should be deleted somewhere else. And in order to be sure, delegate this to system tmpFile?.deleteOnExit() uri = FileProvider.getUriForFile(context, "$APPLICATION_ID.provider", tmpFile!!) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt index 23bd5491f2..c8ad31f760 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt @@ -277,7 +277,7 @@ actual fun getDrawableFromUri(uri: URI, withAlertOnException: Boolean): Any? { actual suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean): File? { return try { val ext = if (asPng) "png" else "jpg" - return File(getTempFilesDirectory() + File.separator + generateNewFileName("IMG", ext)).apply { + return File(tmpDir.absolutePath + File.separator + generateNewFileName("IMG", ext)).apply { outputStream().use { out -> image.asAndroidBitmap().compress(if (asPng) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG, 85, out) out.flush() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 279f84f85b..de77db23ca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -319,8 +319,8 @@ object ChatController { try { if (chatModel.chatRunning.value == true) return apiSetNetworkConfig(getNetCfg()) - apiSetTempFolder(getTempFilesDirectory()) - apiSetFilesFolder(getAppFilesDirectory()) + apiSetTempFolder(coreTmpDir.absolutePath) + apiSetFilesFolder(appFilesDir.absolutePath) apiSetXFTPConfig(getXFTPCfg()) val justStarted = apiStartChat() val users = listUsers() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 8762c1aecd..e37dcb4ae9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -30,9 +30,8 @@ val chatController: ChatController = ChatController suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) { val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() - val dbAbsolutePathPrefix = getFilesDirectory() val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp - val migrated: Array = chatMigrateInit(dbAbsolutePathPrefix, dbKey, confirm.value) + val migrated: Array = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value) val res: DBMigrationResult = kotlin.runCatching { json.decodeFromString(migrated[0] as String) }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index 7cfda123c6..f7e40d7f1c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -9,7 +9,13 @@ import java.net.URI expect val dataDir: File expect val tmpDir: File -expect val cacheDir: File +expect val filesDir: File +expect val appFilesDir: File +expect val coreTmpDir: File +expect val dbAbsolutePrefixPath: String + +expect val chatDatabaseFileName: String +expect val agentDatabaseFileName: String fun copyFileToFile(from: File, to: URI, finally: () -> Unit) { try { @@ -43,21 +49,8 @@ fun copyBytesToFile(bytes: ByteArrayInputStream, to: URI, finally: () -> Unit) { } } -fun getFilesDirectory(): String { - return dataDir.absolutePath + File.separator + "files" -} - -// LALAL -fun getTempFilesDirectory(): String { - return getFilesDirectory() + File.separator + "temp_files" -} - -fun getAppFilesDirectory(): String { - return getFilesDirectory() + File.separator + "app_files" -} - fun getAppFilePath(fileName: String): String { - return getAppFilesDirectory() + File.separator + fileName + return appFilesDir.absolutePath + File.separator + fileName } fun getLoadedFilePath(file: CIFile?): String? { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index fe2a48c1ac..3ad019949a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -25,7 +25,7 @@ import java.util.* @Composable fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTime: Instant) { - val archivePath = getFilesDirectory() + File.separator + archiveName + val archivePath = filesDir.absolutePath + File.separator + archiveName val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? -> if (to != null) { copyFileToFile(File(archivePath), to) {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index ab29816c81..7101481681 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -221,9 +221,8 @@ private fun shouldShowRestoreDbButton(prefs: AppPreferences): Boolean { val startedAt = prefs.encryptionStartedAt.get() ?: return false /** Just in case there is any small difference between reported Java's [Clock.System.now] and Linux's time on a file */ val safeDiffInTime = 10_000L - // LALAL CHANGE FILENAME - val filesChat = File(dataDir.absolutePath + File.separator + "files_chat.db.bak") - val filesAgent = File(dataDir.absolutePath + File.separator + "files_agent.db.bak") + val filesChat = File(dataDir.absolutePath + File.separator + "${chatDatabaseFileName}.bak") + val filesAgent = File(dataDir.absolutePath + File.separator + "${agentDatabaseFileName}.bak") return filesChat.exists() && filesAgent.exists() && startedAt.toEpochMilliseconds() - safeDiffInTime <= filesChat.lastModified() && @@ -231,9 +230,8 @@ private fun shouldShowRestoreDbButton(prefs: AppPreferences): Boolean { } private fun restoreDb(restoreDbFromBackup: MutableState, prefs: AppPreferences) { - // LALAL CHANGE FILENAME - val filesChatBase = dataDir.absolutePath + File.separator + "files_chat.db" - val filesAgentBase = dataDir.absolutePath + File.separator + "files_agent.db" + val filesChatBase = dataDir.absolutePath + File.separator + chatDatabaseFileName + val filesAgentBase = dataDir.absolutePath + File.separator + agentDatabaseFileName try { Files.copy(Path("$filesChatBase.bak"), Path(filesChatBase), StandardCopyOption.REPLACE_EXISTING) Files.copy(Path("$filesAgentBase.bak"), Path(filesAgentBase), StandardCopyOption.REPLACE_EXISTING) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 2b86d67e17..fb41c08302 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -54,7 +54,7 @@ fun DatabaseView( } } } - val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(getAppFilesDirectory())) } + val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(appFilesDir.absolutePath)) } val importArchiveLauncher = rememberFileChooserLauncher(true) { to: URI? -> if (to != null) { importArchiveAlert(m, to, appFilesCountAndSize, progressIndicator) @@ -475,8 +475,8 @@ private suspend fun exportChatArchive( val archiveTime = Clock.System.now() val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant())) val archiveName = "simplex-chat.$ts.zip" - val archivePath = "${getFilesDirectory()}${File.separator}$archiveName" - val config = ArchiveConfig(archivePath, parentTempDirectory = cacheDir.toString()) + val archivePath = "${filesDir.absolutePath}${File.separator}$archiveName" + val config = ArchiveConfig(archivePath, parentTempDirectory = tmpDir.toString()) m.controller.apiExportArchive(config) deleteOldArchive(m) m.controller.appPrefs.chatArchiveName.set(archiveName) @@ -490,7 +490,7 @@ private suspend fun exportChatArchive( private fun deleteOldArchive(m: ChatModel) { val chatArchiveName = m.controller.appPrefs.chatArchiveName.get() if (chatArchiveName != null) { - val file = File("${getFilesDirectory()}${File.separator}$chatArchiveName") + val file = File("${filesDir.absolutePath}${File.separator}$chatArchiveName") val fileDeleted = file.delete() if (fileDeleted) { m.controller.appPrefs.chatArchiveName.set(null) @@ -529,10 +529,10 @@ private fun importArchive( try { m.controller.apiDeleteStorage() try { - val config = ArchiveConfig(archivePath, parentTempDirectory = cacheDir.toString()) + val config = ArchiveConfig(archivePath, parentTempDirectory = tmpDir.toString()) val archiveErrors = m.controller.apiImportArchive(config) DatabaseUtils.ksDatabasePassword.remove() - appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory()) + appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) if (archiveErrors.isEmpty()) { operationEnded(m, progressIndicator) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.chat_database_imported), text = generalGetString(MR.strings.restart_the_app_to_use_imported_chat_database)) @@ -563,7 +563,7 @@ private fun saveArchiveFromURI(importedArchiveURI: URI): String? { val inputStream = importedArchiveURI.inputStream() val archiveName = getFileName(importedArchiveURI) if (inputStream != null && archiveName != null) { - val archivePath = "$cacheDir${File.separator}$archiveName" + val archivePath = "$tmpDir${File.separator}$archiveName" val destFile = File(archivePath) Files.copy(inputStream, destFile.toPath()) archivePath @@ -632,7 +632,7 @@ private fun afterSetCiTTL( appFilesCountAndSize: MutableState>, ) { progressIndicator.value = false - appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory()) + appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) withApi { try { val chats = m.controller.apiGetChats() @@ -655,7 +655,7 @@ private fun deleteFilesAndMediaAlert(appFilesCountAndSize: MutableState>) { deleteAppFiles() - appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory()) + appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) } private fun operationEnded(m: ChatModel, progressIndicator: MutableState, alert: () -> Unit) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index 4c7481ec55..10641b6d81 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -39,9 +39,8 @@ object DatabaseUtils { } } - // LALAL CHANGE DB FILE NAME ON DESKTOP private fun hasDatabase(rootDir: String): Boolean = - File(rootDir + File.separator + "files_chat.db").exists() && File(rootDir + File.separator + "files_agent.db").exists() + File(rootDir + File.separator + chatDatabaseFileName).exists() && File(rootDir + File.separator + agentDatabaseFileName).exists() fun useDatabaseKey(): String { Log.d(TAG, "useDatabaseKey ${appPreferences.storeDBPassphrase.get()}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 74bee1f114..ec25a8811c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -209,7 +209,7 @@ fun removeFile(fileName: String): Boolean { } fun deleteAppFiles() { - val dir = File(getAppFilesDirectory()) + val dir = appFilesDir try { dir.list()?.forEach { removeFile(it) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 826d705e42..7c9201e4e1 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -43,10 +43,10 @@ private fun applyAppLocale() { @Suppress("UnsafeDynamicallyLoadedCode") actual fun initHaskell() { val libApp = "libapp-lib.${desktopPlatform.libExtension}" - val tmpDir = Files.createTempDirectory("simplex-native-libs").toFile() - tmpDir.deleteOnExit() - copyResources(desktopPlatform.libPath, tmpDir.toPath()) - System.load(File(tmpDir, libApp).absolutePath) + val libsTmpDir = File(tmpDir.absolutePath + File.separator + "libs") + copyResources(desktopPlatform.libPath, libsTmpDir.toPath()) + System.load(File(libsTmpDir, libApp).absolutePath) + libsTmpDir.deleteRecursively() initHS() } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index 990cff580f..657d683e78 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -15,9 +15,15 @@ private fun applicationParentPath(): String = try { "./" } -actual val dataDir: File = File(desktopPlatform.configPath) -actual val tmpDir: File = File(System.getProperty("java.io.tmpdir")) -actual val cacheDir: File = tmpDir +actual val dataDir: File = File(desktopPlatform.dataPath) +actual val tmpDir: File = File(System.getProperty("java.io.tmpdir") + File.separator + "simplex").also { it.deleteOnExit() } +actual val filesDir: File = File(dataDir.absolutePath + File.separator + "simplex_v1_files") +actual val appFilesDir: File = filesDir +actual val coreTmpDir: File = File(dataDir.absolutePath + File.separator + "tmp") +actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "simplex_v1" + +actual val chatDatabaseFileName: String = "simplex_v1_chat.db" +actual val agentDatabaseFileName: String = "simplex_v1_agent.db" @Composable actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher = diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt index 13dd657820..0a49ed5beb 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt @@ -4,14 +4,16 @@ import java.io.File import java.util.* private val home = System.getProperty("user.home") +private val unixConfigPath = (System.getenv("XDG_CONFIG_HOME") ?: "$home/.config") + "/simplex" +private val unixDataPath = (System.getenv("XDG_DATA_HOME") ?: "$home/.local/share") + "/simplex" val desktopPlatform = detectDesktopPlatform() -enum class DesktopPlatform(val libPath: String, val libExtension: String, val configPath: String) { - LINUX_X86_64("/libs/linux-x86_64", "so", "$home/.config/simplex"), - LINUX_AARCH64("/libs/aarch64", "so", "$home/.config/simplex"), - WINDOWS_X86_64("/libs/windows-x86_64", "dll", System.getenv("AppData") + File.separator + "simplex"), - MAC_X86_64("/libs/mac-x86_64", "dylib", "$home/.config/simplex"), - MAC_AARCH64("/libs/mac-aarch64", "dylib", "$home/.config/simplex"); +enum class DesktopPlatform(val libPath: String, val libExtension: String, val configPath: String, val dataPath: String) { + LINUX_X86_64("/libs/linux-x86_64", "so", unixConfigPath, unixDataPath), + LINUX_AARCH64("/libs/aarch64", "so", unixConfigPath, unixDataPath), + WINDOWS_X86_64("/libs/windows-x86_64", "dll", System.getenv("AppData") + File.separator + "SimpleX", System.getenv("AppData") + File.separator + "SimpleX"), + MAC_X86_64("/libs/mac-x86_64", "dylib", unixConfigPath, unixDataPath), + MAC_AARCH64("/libs/mac-aarch64", "dylib", unixConfigPath, unixDataPath); } private fun detectDesktopPlatform(): DesktopPlatform { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt index 36aef2437e..47d7829749 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt @@ -20,7 +20,7 @@ actual fun escapedHtmlToAnnotatedString(text: String, density: Density): Annotat } actual fun getAppFileUri(fileName: String): URI = - URI("file:" + getAppFilesDirectory() + File.separator + fileName) + URI("file:" + appFilesDir.absolutePath + File.separator + fileName) actual fun getLoadedImage(file: CIFile?): ImageBitmap? { val filePath = getLoadedFilePath(file) @@ -34,7 +34,7 @@ actual fun getLoadedImage(file: CIFile?): ImageBitmap? { actual fun getFileName(uri: URI): String? = uri.toPath().toFile().name -actual fun getAppFilePath(uri: URI): String? = getFilesDirectory() + File.separator + "app_files" +actual fun getAppFilePath(uri: URI): String? = uri.path actual fun getFileSize(uri: URI): Long? = uri.toPath().toFile().length() diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt index a004998e0d..3b53d83a7a 100644 --- a/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt @@ -4,5 +4,6 @@ import chat.simplex.common.showApp fun main() { initHaskell() initApp() + tmpDir.deleteRecursively() return showApp() } From a7a66c2b55eb8382e543f92711712d5e22a34f4a Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:28:13 +0300 Subject: [PATCH 05/18] multiplatform: adjusted database export path (#2698) --- .../kotlin/chat/simplex/common/platform/Files.android.kt | 2 ++ .../commonMain/kotlin/chat/simplex/common/platform/Files.kt | 6 ++++++ .../chat/simplex/common/views/database/DatabaseView.kt | 6 +++--- .../kotlin/chat/simplex/common/platform/Files.desktop.kt | 2 ++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt index f809b82caa..c044e0ad8b 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -21,6 +21,8 @@ actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator actual val chatDatabaseFileName: String = "files_chat.db" actual val agentDatabaseFileName: String = "files_agent.db" +actual val databaseExportDir: File = androidAppContext.cacheDir + @Composable actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher { val launcher = rememberLauncherForActivityResult( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index f7e40d7f1c..ccaeef416d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -17,6 +17,12 @@ expect val dbAbsolutePrefixPath: String expect val chatDatabaseFileName: String expect val agentDatabaseFileName: String +/** +* This is used only for temporary storing db archive for export. +* Providing [tmpDir] instead crashes the app. Check db export before moving from this path to something else +* */ +expect val databaseExportDir: File + fun copyFileToFile(from: File, to: URI, finally: () -> Unit) { try { to.outputStream().use { stream -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index fb41c08302..277d3ebe67 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -476,7 +476,7 @@ private suspend fun exportChatArchive( val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant())) val archiveName = "simplex-chat.$ts.zip" val archivePath = "${filesDir.absolutePath}${File.separator}$archiveName" - val config = ArchiveConfig(archivePath, parentTempDirectory = tmpDir.toString()) + val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) m.controller.apiExportArchive(config) deleteOldArchive(m) m.controller.appPrefs.chatArchiveName.set(archiveName) @@ -529,7 +529,7 @@ private fun importArchive( try { m.controller.apiDeleteStorage() try { - val config = ArchiveConfig(archivePath, parentTempDirectory = tmpDir.toString()) + val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) val archiveErrors = m.controller.apiImportArchive(config) DatabaseUtils.ksDatabasePassword.remove() appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) @@ -563,7 +563,7 @@ private fun saveArchiveFromURI(importedArchiveURI: URI): String? { val inputStream = importedArchiveURI.inputStream() val archiveName = getFileName(importedArchiveURI) if (inputStream != null && archiveName != null) { - val archivePath = "$tmpDir${File.separator}$archiveName" + val archivePath = "$databaseExportDir${File.separator}$archiveName" val destFile = File(archivePath) Files.copy(inputStream, destFile.toPath()) archivePath diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index 657d683e78..9792f57726 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -25,6 +25,8 @@ actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator actual val chatDatabaseFileName: String = "simplex_v1_chat.db" actual val agentDatabaseFileName: String = "simplex_v1_agent.db" +actual val databaseExportDir: File = tmpDir + @Composable actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher = remember { FileChooserLauncher(getContent, onResult) } From d61ff0f2a75f2eebadda00b33c908387784a17bb Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Jul 2023 00:52:18 +0300 Subject: [PATCH 06/18] desktop: expanded dropdown menu (#2687) Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../helpers/DefaultDropDownMenu.desktop.kt | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt index e47fa4ae8b..993025a115 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt @@ -1,10 +1,15 @@ package chat.simplex.common.views.helpers -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material.DropdownMenu import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import kotlin.math.exp actual interface DefaultExposedDropdownMenuBoxScope { @Composable @@ -14,9 +19,11 @@ actual interface DefaultExposedDropdownMenuBoxScope { modifier: Modifier, content: @Composable ColumnScope.() -> Unit ) { - Column { - content() - } + DropdownMenu(expanded, onDismissRequest, offset = DpOffset(0.dp, (-40).dp)) { + Column { + content() + } + } } } @@ -27,8 +34,10 @@ actual fun DefaultExposedDropdownMenuBox( modifier: Modifier, content: @Composable DefaultExposedDropdownMenuBoxScope.() -> Unit ) { - if (expanded) { - val obj = remember { object : DefaultExposedDropdownMenuBoxScope {} } + val obj = remember { object : DefaultExposedDropdownMenuBoxScope {} } + Box(Modifier + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { onExpandedChange(!expanded) }) + ) { obj.content() } } From 2389e870b32dd892bfb2bb006ccadf2dfa0b24da Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:36:42 +0300 Subject: [PATCH 07/18] desktop: do not show scan QR code screen (#2686) * desktop: do not show scan QR code screen * expect/actual and different button text --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../newchat/ConnectViaLinkView.android.kt | 66 +++++++++++++++++++ .../common/views/chat/VerifyCodeView.kt | 9 ++- .../views/newchat/ConnectViaLinkView.kt | 62 +---------------- .../common/views/newchat/NewChatSheet.kt | 9 ++- .../views/usersettings/ProtocolServersView.kt | 23 ++++--- .../newchat/ConnectViaLinkView.desktop.kt | 9 +++ 6 files changed, 101 insertions(+), 77 deletions(-) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt new file mode 100644 index 0000000000..dcb5055425 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt @@ -0,0 +1,66 @@ +package chat.simplex.common.views.newchat + +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.unit.sp +import chat.simplex.common.model.ChatModel +import chat.simplex.res.MR + +@Composable +actual fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) { + val selection = remember { + mutableStateOf( + runCatching { ConnectViaLinkTab.valueOf(m.controller.appPrefs.connectViaLinkTab.get()!!) }.getOrDefault(ConnectViaLinkTab.SCAN) + ) + } + val tabTitles = ConnectViaLinkTab.values().map { + when (it) { + ConnectViaLinkTab.SCAN -> stringResource(MR.strings.scan_QR_code) + ConnectViaLinkTab.PASTE -> stringResource(MR.strings.paste_the_link_you_received) + } + } + Column( + Modifier.fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column(Modifier.weight(1f)) { + when (selection.value) { + ConnectViaLinkTab.SCAN -> { + ScanToConnectView(m, close) + } + ConnectViaLinkTab.PASTE -> { + PasteToConnectView(m, close) + } + } + } + TabRow( + selectedTabIndex = selection.value.ordinal, + backgroundColor = Color.Transparent, + contentColor = MaterialTheme.colors.primary, + ) { + tabTitles.forEachIndexed { index, it -> + Tab( + selected = selection.value.ordinal == index, + onClick = { + selection.value = ConnectViaLinkTab.values()[index] + m.controller.appPrefs.connectViaLinkTab.set(selection.value .name) + }, + text = { Text(it, fontSize = 13.sp) }, + icon = { + Icon( + if (ConnectViaLinkTab.SCAN.ordinal == index) painterResource(MR.images.ic_qr_code) else painterResource(MR.images.ic_article), + it + ) + }, + selectedContentColor = MaterialTheme.colors.primary, + unselectedContentColor = MaterialTheme.colors.secondary, + ) + } + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index 58e88ec12e..bb79fce66c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -16,6 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.platform.appPlatform import chat.simplex.common.platform.shareText import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -110,9 +111,11 @@ private fun VerifyCodeLayout( verifyCode(null) {} } } else { - SimpleButton(generalGetString(MR.strings.scan_code), painterResource(MR.images.ic_qr_code)) { - ModalManager.shared.showModal { - ScanCodeView(verifyCode) { } + if (appPlatform.isAndroid) { + SimpleButton(generalGetString(MR.strings.scan_code), painterResource(MR.images.ic_qr_code)) { + ModalManager.shared.showModal { + ScanCodeView(verifyCode) { } + } } } SimpleButton(generalGetString(MR.strings.mark_code_verified), painterResource(MR.images.ic_verified_user)) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt index 5418ddc02e..a2bc2c4df4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt @@ -1,71 +1,11 @@ package chat.simplex.common.views.newchat -import androidx.compose.foundation.layout.* -import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatModel -import chat.simplex.common.views.newchat.ScanToConnectView -import chat.simplex.res.MR enum class ConnectViaLinkTab { SCAN, PASTE } @Composable -fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) { - val selection = remember { - mutableStateOf( - runCatching { ConnectViaLinkTab.valueOf(m.controller.appPrefs.connectViaLinkTab.get()!!) }.getOrDefault(ConnectViaLinkTab.SCAN) - ) - } - val tabTitles = ConnectViaLinkTab.values().map { - when (it) { - ConnectViaLinkTab.SCAN -> stringResource(MR.strings.scan_QR_code) - ConnectViaLinkTab.PASTE -> stringResource(MR.strings.paste_the_link_you_received) - } - } - Column( - Modifier.fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween - ) { - Column(Modifier.weight(1f)) { - when (selection.value) { - ConnectViaLinkTab.SCAN -> { - ScanToConnectView(m, close) - } - ConnectViaLinkTab.PASTE -> { - PasteToConnectView(m, close) - } - } - } - TabRow( - selectedTabIndex = selection.value.ordinal, - backgroundColor = Color.Transparent, - contentColor = MaterialTheme.colors.primary, - ) { - tabTitles.forEachIndexed { index, it -> - Tab( - selected = selection.value.ordinal == index, - onClick = { - selection.value = ConnectViaLinkTab.values()[index] - m.controller.appPrefs.connectViaLinkTab.set(selection.value .name) - }, - text = { Text(it, fontSize = 13.sp) }, - icon = { - Icon( - if (ConnectViaLinkTab.SCAN.ordinal == index) painterResource(MR.images.ic_qr_code) else painterResource(MR.images.ic_article), - it - ) - }, - selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = MaterialTheme.colors.secondary, - ) - } - } - } -} +expect fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 32d5986515..a51d070f85 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -1,6 +1,5 @@ package chat.simplex.common.views.newchat -import chat.simplex.common.platform.BackHandler import androidx.compose.animation.* import androidx.compose.animation.core.* import androidx.compose.desktop.ui.tooling.preview.Preview @@ -24,7 +23,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatModel -import chat.simplex.common.platform.screenWidth +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @@ -55,7 +54,11 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow - ScanProtocolServer { - close() - servers = servers + it - m.userSMPServersUnsaved.value = servers + if (appPlatform.isAndroid) { + SectionItemView({ + AlertManager.shared.hideAlert() + ModalManager.shared.showModalCloseable { close -> + ScanProtocolServer { + close() + servers = servers + it + m.userSMPServersUnsaved.value = servers + } } } - } - ) { - Text(stringResource(MR.strings.smp_servers_scan_qr), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + ) { + Text(stringResource(MR.strings.smp_servers_scan_qr), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } } val hasAllPresets = hasAllPresets(presetServers, servers, m) if (!hasAllPresets) { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt new file mode 100644 index 0000000000..84da0a7757 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.desktop.kt @@ -0,0 +1,9 @@ +package chat.simplex.common.views.newchat + +import androidx.compose.runtime.* +import chat.simplex.common.model.ChatModel + +@Composable +actual fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) { + PasteToConnectView(m, close) +} From 27f4661ac425ed7f0c404f23f40438e65afdbd77 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:26:37 +0300 Subject: [PATCH 08/18] desktop: send images and files (#2691) * desktop: send images and files * expect/actual * file filter on non-Linux OSes --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../simplex/common/platform/Files.android.kt | 21 ++++++++ .../helpers/ChooseAttachmentView.android.kt | 30 +++++++++++ .../chat/simplex/common/platform/Files.kt | 6 +++ .../simplex/common/views/chat/ComposeView.kt | 4 +- .../common/views/database/ChatArchiveView.kt | 2 +- .../common/views/database/DatabaseView.kt | 2 +- .../views/helpers/ChooseAttachmentView.kt | 29 ++-------- .../commonMain/resources/MR/base/strings.xml | 1 + .../kotlin/chat/simplex/common/DesktopApp.kt | 28 ++++++++-- .../simplex/common/platform/Files.desktop.kt | 53 +++++++++++++++++-- .../simplex/common/platform/Images.desktop.kt | 15 +++++- .../simplex/common/platform/Videos.desktop.kt | 13 +++++ .../common/views/chat/ComposeView.desktop.kt | 24 +++++++-- .../helpers/ChooseAttachmentView.desktop.kt | 22 ++++++++ .../views/helpers/DefaultDialog.desktop.kt | 52 ++++++++++++------ .../views/helpers/GetImageView.desktop.kt | 7 +-- 16 files changed, 248 insertions(+), 61 deletions(-) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.android.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.desktop.kt diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt index c044e0ad8b..213d05dcb0 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -32,6 +32,15 @@ actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> return FileChooserLauncher(launcher) } +@Composable +actual fun rememberFileChooserMultipleLauncher(onResult: (List) -> Unit): FileChooserMultipleLauncher { + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetMultipleContents(), + onResult = { onResult(it.map { it.toURI() }) } + ) + return FileChooserMultipleLauncher(launcher) +} + actual class FileChooserLauncher actual constructor() { private lateinit var launcher: ManagedActivityResultLauncher @@ -44,5 +53,17 @@ actual class FileChooserLauncher actual constructor() { } } +actual class FileChooserMultipleLauncher actual constructor() { + private lateinit var launcher: ManagedActivityResultLauncher> + + constructor(launcher: ManagedActivityResultLauncher>): this() { + this.launcher = launcher + } + + actual suspend fun launch(input: String) { + launcher.launch(input) + } +} + actual fun URI.inputStream(): InputStream? = androidAppContext.contentResolver.openInputStream(toUri()) actual fun URI.outputStream(): OutputStream = androidAppContext.contentResolver.openOutputStream(toUri())!! diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.android.kt new file mode 100644 index 0000000000..3dace5f9bc --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.android.kt @@ -0,0 +1,30 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import chat.simplex.common.views.newchat.ActionButton +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun ChooseAttachmentButtons(attachmentOption: MutableState, hide: () -> Unit) { + ActionButton(Modifier.fillMaxWidth(0.25f), null, stringResource(MR.strings.use_camera_button), icon = painterResource(MR.images.ic_camera_enhance)) { + attachmentOption.value = AttachmentOption.CameraPhoto + hide() + } + ActionButton(Modifier.fillMaxWidth(0.33f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) { + attachmentOption.value = AttachmentOption.GalleryImage + hide() + } + ActionButton(Modifier.fillMaxWidth(0.50f), null, stringResource(MR.strings.gallery_video_button), icon = painterResource(MR.images.ic_smart_display)) { + attachmentOption.value = AttachmentOption.GalleryVideo + hide() + } + ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(MR.strings.choose_file), icon = painterResource(MR.images.ic_note_add)) { + attachmentOption.value = AttachmentOption.File + hide() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index ccaeef416d..b02c86579f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -71,9 +71,15 @@ fun getLoadedFilePath(file: CIFile?): String? { @Composable expect fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher +expect fun rememberFileChooserMultipleLauncher(onResult: (List) -> Unit): FileChooserMultipleLauncher + expect class FileChooserLauncher() { suspend fun launch(input: String) } +expect class FileChooserMultipleLauncher() { + suspend fun launch(input: String) +} + expect fun URI.inputStream(): InputStream? expect fun URI.outputStream(): OutputStream diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index c16a252a9c..6912ced0e8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -175,12 +175,12 @@ fun ComposeView( val content = ArrayList() val imagesPreview = ArrayList() uris.forEach { uri -> - var bitmap: ImageBitmap? = null + var bitmap: ImageBitmap? when { isImage(uri) -> { // Image val drawable = getDrawableFromUri(uri) - bitmap = if (drawable != null) getBitmapFromUri(uri) else null + bitmap = getBitmapFromUri(uri) if (isAnimImage(uri, drawable)) { // It's a gif or webp val fileSize = getFileSize(uri) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index 3ad019949a..3963c69ae3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -34,7 +34,7 @@ fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTim ChatArchiveLayout( title, archiveTime, - saveArchive = { withApi { saveArchiveLauncher.launch(archivePath.substringAfterLast("/")) }}, + saveArchive = { withApi { saveArchiveLauncher.launch(archivePath.substringAfterLast(File.separator)) }}, deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 277d3ebe67..32d2379f75 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -457,7 +457,7 @@ private fun exportArchive( try { val archiveFile = exportChatArchive(m, chatArchiveName, chatArchiveTime, chatArchiveFile) chatArchiveFile.value = archiveFile - saveArchiveLauncher.launch(archiveFile.substringAfterLast("/")) + saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) progressIndicator.value = false } catch (e: Error) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_exporting_chat_database), e.toString()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt index 5eee8d99d2..aa3c4560ea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt @@ -5,11 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import chat.simplex.common.views.newchat.ActionButton -import chat.simplex.res.MR sealed class AttachmentOption { object CameraPhoto: AttachmentOption() @@ -19,10 +15,7 @@ sealed class AttachmentOption { } @Composable -fun ChooseAttachmentView( - attachmentOption: MutableState, - hide: () -> Unit -) { +fun ChooseAttachmentView(attachmentOption: MutableState, hide: () -> Unit) { Box( modifier = Modifier .fillMaxWidth() @@ -37,22 +30,10 @@ fun ChooseAttachmentView( .padding(horizontal = 8.dp, vertical = 30.dp), horizontalArrangement = Arrangement.SpaceEvenly ) { - ActionButton(Modifier.fillMaxWidth(0.25f), null, stringResource(MR.strings.use_camera_button), icon = painterResource(MR.images.ic_camera_enhance)) { - attachmentOption.value = AttachmentOption.CameraPhoto - hide() - } - ActionButton(Modifier.fillMaxWidth(0.33f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) { - attachmentOption.value = AttachmentOption.GalleryImage - hide() - } - ActionButton(Modifier.fillMaxWidth(0.50f), null, stringResource(MR.strings.gallery_video_button), icon = painterResource(MR.images.ic_smart_display)) { - attachmentOption.value = AttachmentOption.GalleryVideo - hide() - } - ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(MR.strings.choose_file), icon = painterResource(MR.images.ic_note_add)) { - attachmentOption.value = AttachmentOption.File - hide() - } + ChooseAttachmentButtons(attachmentOption, hide) } } } + +@Composable +expect fun ChooseAttachmentButtons(attachmentOption: MutableState, hide: () -> Unit) 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 5da9bd973c..064c62d3cd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -394,6 +394,7 @@ Camera From Gallery File + Choose a file Image Video diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index ffe9bd81a6..4c5cfb2526 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -38,8 +38,20 @@ fun showApp() = application { FileDialogChooser( title = "SimpleX", isLoad = true, + params = simplexWindowState.openDialog.params, onResult = { - simplexWindowState.openDialog.onResult(it) + simplexWindowState.openDialog.onResult(it.firstOrNull()) + } + ) + } + + if (simplexWindowState.openMultipleDialog.isAwaiting) { + FileDialogChooser( + title = "SimpleX", + isLoad = true, + params = simplexWindowState.openMultipleDialog.params, + onResult = { + simplexWindowState.openMultipleDialog.onResult(it) } ) } @@ -48,7 +60,8 @@ fun showApp() = application { FileDialogChooser( title = "SimpleX", isLoad = false, - onResult = { simplexWindowState.saveDialog.onResult(it) } + params = simplexWindowState.saveDialog.params, + onResult = { simplexWindowState.saveDialog.onResult(it.firstOrNull()) } ) } val toasts = remember { simplexWindowState.toasts } @@ -97,16 +110,25 @@ fun showApp() = application { class SimplexWindowState { val backstack = mutableStateListOf<() -> Unit>() val openDialog = DialogState() + val openMultipleDialog = DialogState>() val saveDialog = DialogState() val toasts = mutableStateListOf>() } +data class DialogParams( + val allowMultiple: Boolean = false, + val fileFilter: ((File?) -> Boolean)? = null, + val fileFilterDescription: String = "", +) + class DialogState { private var onResult: CompletableDeferred? by mutableStateOf(null) + var params = DialogParams() val isAwaiting get() = onResult != null - suspend fun awaitResult(): T { + suspend fun awaitResult(params: DialogParams = DialogParams()): T { onResult = CompletableDeferred() + this.params = params val result = onResult!!.await() onResult = null return result diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index 9792f57726..023683fece 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -1,8 +1,9 @@ package chat.simplex.common.platform import androidx.compose.runtime.* -import chat.simplex.common.DesktopApp -import chat.simplex.common.simplexWindowState +import chat.simplex.common.* +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.res.MR import java.io.* import java.net.URI @@ -31,6 +32,10 @@ actual val databaseExportDir: File = tmpDir actual fun rememberFileChooserLauncher(getContent: Boolean, onResult: (URI?) -> Unit): FileChooserLauncher = remember { FileChooserLauncher(getContent, onResult) } +@Composable +actual fun rememberFileChooserMultipleLauncher(onResult: (List) -> Unit): FileChooserMultipleLauncher = + remember { FileChooserMultipleLauncher(onResult) } + actual class FileChooserLauncher actual constructor() { var getContent: Boolean = false lateinit var onResult: (URI?) -> Unit @@ -41,10 +46,50 @@ actual class FileChooserLauncher actual constructor() { } actual suspend fun launch(input: String) { - val res = if (getContent) simplexWindowState.openDialog.awaitResult() else simplexWindowState.saveDialog.awaitResult() - onResult(if (!getContent && input.isNotEmpty() && res != null) File(res, input).toURI() else res?.toURI()) + val res = if (getContent) { + val params = DialogParams( + allowMultiple = false, + fileFilter = fileFilter(input), + fileFilterDescription = fileFilterDescription(input), + ) + simplexWindowState.openDialog.awaitResult(params) + } else { + simplexWindowState.saveDialog.awaitResult() + } + onResult(res?.toURI()) } } +actual class FileChooserMultipleLauncher actual constructor() { + lateinit var onResult: (List) -> Unit + + constructor(onResult: (List) -> Unit): this() { + this.onResult = onResult + } + + actual suspend fun launch(input: String) { + val params = DialogParams( + allowMultiple = true, + fileFilter = fileFilter(input), + fileFilterDescription = fileFilterDescription(input), + ) + onResult(simplexWindowState.openMultipleDialog.awaitResult(params).map { it.toURI() }) + } +} + +private fun fileFilter(input: String): (File?) -> Boolean = when(input) { + "image/*" -> { file -> if (file?.isDirectory == true) true else if (file != null) isImage(file.toURI()) else false } + "video/*" -> { file -> if (file?.isDirectory == true) true else if (file != null) isVideo(file.toURI()) else false } + "*/*" -> { _ -> true } + else -> { _ -> true } +} + +private fun fileFilterDescription(input: String): String = when(input) { + "image/*" -> generalGetString(MR.strings.gallery_image_button) + "video/*" -> generalGetString(MR.strings.gallery_video_button) + "*/*" -> generalGetString(MR.strings.choose_file) + else -> "" +} + actual fun URI.inputStream(): InputStream? = File(URI("file:" + toString().removePrefix("file:"))).inputStream() actual fun URI.outputStream(): OutputStream = File(URI("file:" + toString().removePrefix("file:"))).outputStream() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index 2d6011c223..81699a6cc1 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -41,7 +41,20 @@ actual fun resizeImageToStrSize(image: ImageBitmap, maxDataSize: Long): String { } return str } -actual fun resizeImageToDataSize(image: ImageBitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream = TODO() +actual fun resizeImageToDataSize(image: ImageBitmap, usePng: Boolean, maxDataSize: Long): ByteArrayOutputStream { + var img = image + var stream = compressImageData(img, usePng) + while (stream.size() > maxDataSize) { + val ratio = sqrt(stream.size().toDouble() / maxDataSize.toDouble()) + val clippedRatio = kotlin.math.min(ratio, 2.0) + val width = (img.width.toDouble() / clippedRatio).toInt() + val height = img.height * width / img.width + img = img.scale(width, height) + stream = compressImageData(img, usePng) + } + return stream +} + actual fun cropToSquare(image: ImageBitmap): ImageBitmap { var xOffset = 0 var yOffset = 0 diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt new file mode 100644 index 0000000000..54a511e082 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt @@ -0,0 +1,13 @@ +package chat.simplex.common.platform + +import java.net.URI + +fun isVideo(uri: URI): Boolean { + val path = uri.path.lowercase() + return path.endsWith(".mov") || + path.endsWith(".avi") || + path.endsWith(".mp4") || + path.endsWith(".mpg") || + path.endsWith(".mpeg") || + path.endsWith(".mkv") +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt index c7eeef3413..dd9f3dd612 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/ComposeView.desktop.kt @@ -1,7 +1,8 @@ package chat.simplex.common.views.chat import androidx.compose.runtime.* -import chat.simplex.common.views.helpers.AttachmentOption +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.* import java.net.URI @Composable @@ -11,12 +12,27 @@ actual fun AttachmentSelection( processPickedFile: (URI?, String?) -> Unit, processPickedMedia: (List, String?) -> Unit ) { + val imageLauncher = rememberFileChooserMultipleLauncher { + processPickedMedia(it, null) + } + val videoLauncher = rememberFileChooserMultipleLauncher { + processPickedMedia(it, null) + } + val filesLauncher = rememberFileChooserLauncher(true) { + if (it != null) processPickedFile(it, null) + } LaunchedEffect(attachmentOption.value) { when (attachmentOption.value) { AttachmentOption.CameraPhoto -> {} - AttachmentOption.GalleryImage -> {} - AttachmentOption.GalleryVideo -> {} - AttachmentOption.File -> {} + AttachmentOption.GalleryImage -> { + imageLauncher.launch("image/*") + } + AttachmentOption.GalleryVideo -> { + videoLauncher.launch("video/*") + } + AttachmentOption.File -> { + filesLauncher.launch("*/*") + } else -> {} } attachmentOption.value = null diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.desktop.kt new file mode 100644 index 0000000000..32f5a0a80c --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.desktop.kt @@ -0,0 +1,22 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import chat.simplex.common.views.newchat.ActionButton +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun ChooseAttachmentButtons(attachmentOption: MutableState, hide: () -> Unit) { + ActionButton(Modifier.fillMaxWidth(0.5f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) { + attachmentOption.value = AttachmentOption.GalleryImage + hide() + } + ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(MR.strings.choose_file), icon = painterResource(MR.images.ic_note_add)) { + attachmentOption.value = AttachmentOption.File + hide() + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt index ade674dff2..58edc0d88b 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDialog.desktop.kt @@ -3,6 +3,8 @@ package chat.simplex.common.views.helpers import androidx.compose.runtime.* import androidx.compose.ui.input.key.* import androidx.compose.ui.window.* +import chat.simplex.common.DialogParams +import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.awt.FileDialog @@ -35,13 +37,13 @@ actual fun DefaultDialog( fun FrameWindowScope.FileDialogChooser( title: String, isLoad: Boolean, - extensions: List = emptyList(), - onResult: (result: File?) -> Unit + params: DialogParams, + onResult: (result: List) -> Unit ) { if (isLinux()) { - FileDialogChooserMultiple(title, isLoad, extensions) { onResult(it.firstOrNull()) } + FileDialogChooserMultiple(title, isLoad, params.allowMultiple, params.fileFilter, params.fileFilterDescription, onResult) } else { - FileDialogAwt(title, isLoad, onResult) + FileDialogAwt(title, isLoad, params.allowMultiple, params.fileFilter, onResult) } } @@ -49,7 +51,9 @@ fun FrameWindowScope.FileDialogChooser( fun FrameWindowScope.FileDialogChooserMultiple( title: String, isLoad: Boolean, - extensions: List = emptyList(), + allowMultiple: Boolean, + fileFilter: ((File?) -> Boolean)? = null, + fileFilterDescription: String? = null, onResult: (result: List) -> Unit ) { val scope = rememberCoroutineScope() @@ -57,9 +61,15 @@ fun FrameWindowScope.FileDialogChooserMultiple( val job = scope.launch(Dispatchers.Main) { val fileChooser = JFileChooser() fileChooser.dialogTitle = title - fileChooser.isMultiSelectionEnabled = isLoad - fileChooser.isAcceptAllFileFilterUsed = extensions.isEmpty() - extensions.forEach { fileChooser.addChoosableFileFilter(it) } + fileChooser.isMultiSelectionEnabled = allowMultiple && isLoad + fileChooser.isAcceptAllFileFilterUsed = fileFilter == null + if (fileFilter != null && fileFilterDescription != null) { + fileChooser.addChoosableFileFilter(object: FileFilter() { + override fun accept(file: File?): Boolean = fileFilter(file) + + override fun getDescription(): String = fileFilterDescription + }) + } val returned = if (isLoad) { fileChooser.showOpenDialog(window) } else { @@ -69,7 +79,11 @@ fun FrameWindowScope.FileDialogChooserMultiple( val result = when (returned) { JFileChooser.APPROVE_OPTION -> { if (isLoad) { - fileChooser.selectedFiles.filter { it.canRead() } + when { + allowMultiple -> fileChooser.selectedFiles.filter { it.canRead() } + fileChooser.selectedFile != null && fileChooser.selectedFile.canRead() -> listOf(fileChooser.selectedFile) + else -> emptyList() + } } else { if (!fileChooser.fileFilter.accept(fileChooser.selectedFile)) { val ext = (fileChooser.fileFilter as FileNameExtensionFilter).extensions[0] @@ -78,7 +92,7 @@ fun FrameWindowScope.FileDialogChooserMultiple( listOf(fileChooser.selectedFile) } } - else -> listOf(); + else -> emptyList() } onResult(result) } @@ -95,22 +109,30 @@ fun FrameWindowScope.FileDialogChooserMultiple( private fun FrameWindowScope.FileDialogAwt( title: String, isLoad: Boolean, - onResult: (result: File?) -> Unit + allowMultiple: Boolean, + fileFilter: ((File?) -> Boolean)? = null, + onResult: (result: List) -> Unit ) = AwtWindow( create = { - object: FileDialog(window, "Choose a file", if (isLoad) LOAD else SAVE) { + object: FileDialog(window, generalGetString(MR.strings.choose_file_title), if (isLoad) LOAD else SAVE) { override fun setVisible(value: Boolean) { super.setVisible(value) if (value) { - if (file != null) { - onResult(File(directory).resolve(file)) + if (files != null) { + onResult(files.toList()) } else { - onResult(null) + onResult(emptyList()) } } } }.apply { this.title = title + this.isMultipleMode = allowMultiple && isLoad + if (fileFilter != null) { + this.setFilenameFilter { dir, file -> + fileFilter(File(dir.absolutePath + File.separator + file)) + } + } } }, dispose = FileDialog::dispose diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt index d824627449..544c15abe8 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/GetImageView.desktop.kt @@ -44,13 +44,8 @@ actual fun GetImageBottomSheet( } } val pickImageLauncher = rememberFileChooserLauncher(true, processPickedImage) - // LALAL - /*ActionButton(null, stringResource(MR.strings.use_camera_button), icon = painterResource(MR.images.ic_photo_camera)) { - hideBottomSheet() - }*/ ActionButton(null, stringResource(MR.strings.from_gallery_button), icon = painterResource(MR.images.ic_image)) { - // LALAL support providing file extensions - withApi { pickImageLauncher.launch("") } + withApi { pickImageLauncher.launch("image/*") } } } } From f5825d20e498ecf3cb08b9649afcfc7a8ade162c Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Thu, 20 Jul 2023 20:43:17 +0700 Subject: [PATCH 09/18] follow up --- .../src/commonMain/kotlin/chat/simplex/common/App.kt | 2 +- .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../common/views/usersettings/PrivacySettings.kt | 4 ++++ .../views/usersettings/SetDeliveryReceiptsView.kt | 12 ++++++------ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 7918cfcf21..c2ff62442a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp -import chat.simplex.app.views.usersettings.SetDeliveryReceiptsView +import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_PADDING diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 2f562530ca..065003a072 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -246,7 +246,7 @@ class AppPreferences { private const val SHARED_PREFS_PRIVACY_LINK_PREVIEWS = "PrivacyLinkPreviews" private const val SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE = "PrivacySimplexLinkMode" private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet" - internal const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup" + const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup" private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls" private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites" private const val SHARED_PREFS_CHAT_ARCHIVE_NAME = "ChatArchiveName" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 861581bc06..0b00df1c43 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -11,6 +11,10 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.* +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index 04fe102ddd..1189c8bba6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -1,7 +1,6 @@ -package chat.simplex.app.views.usersettings +package chat.simplex.common.views.usersettings import SectionBottomSpacer -import android.util.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -12,11 +11,12 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign -import chat.simplex.app.TAG -import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.* +import chat.simplex.common.model.ChatModel import chat.simplex.res.MR +import chat.simplex.common.platform.TAG +import chat.simplex.common.platform.Log +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.helpers.* @Composable fun SetDeliveryReceiptsView(m: ChatModel) { From a715e847add6e4d9fbb2c955eda615e68cc99920 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 21 Jul 2023 21:32:28 +0100 Subject: [PATCH 10/18] core: split preferences to separate file --- simplex-chat.cabal | 2 + src/Simplex/Chat.hs | 2 + src/Simplex/Chat/Call.hs | 3 +- src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/Markdown.hs | 1 + src/Simplex/Chat/Messages.hs | 1 + src/Simplex/Chat/Messages/CIContent.hs | 2 + src/Simplex/Chat/Protocol.hs | 1 + src/Simplex/Chat/Store/Connections.hs | 1 + src/Simplex/Chat/Store/Direct.hs | 1 + src/Simplex/Chat/Store/Groups.hs | 1 + src/Simplex/Chat/Store/Profiles.hs | 1 + src/Simplex/Chat/Store/Shared.hs | 1 + src/Simplex/Chat/Types.hs | 775 +----------------------- src/Simplex/Chat/Types/Preferences.hs | 778 +++++++++++++++++++++++++ src/Simplex/Chat/Types/Util.hs | 30 + src/Simplex/Chat/View.hs | 1 + tests/ChatTests/Utils.hs | 1 + tests/ProtocolTests.hs | 1 + 19 files changed, 834 insertions(+), 770 deletions(-) create mode 100644 src/Simplex/Chat/Types/Preferences.hs create mode 100644 src/Simplex/Chat/Types/Util.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 526051edb6..ba68c8621e 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -125,6 +125,8 @@ library Simplex.Chat.Terminal.Notification Simplex.Chat.Terminal.Output Simplex.Chat.Types + Simplex.Chat.Types.Preferences + Simplex.Chat.Types.Util Simplex.Chat.Util Simplex.Chat.View other-modules: diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9f4988271d..df8120b9cb 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -67,6 +67,8 @@ import Simplex.Chat.Store.Messages import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences +import Simplex.Chat.Types.Util import Simplex.FileTransfer.Client.Main (maxFileSize) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.FileTransfer.Description (ValidFileDescription, gb, kb, mb) diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index 4483e701b8..7a738512bd 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -21,7 +21,8 @@ import Data.Time.Clock (UTCTime) import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) -import Simplex.Chat.Types (Contact, ContactId, User, decodeJSON, encodeJSON) +import Simplex.Chat.Types (Contact, ContactId, User) +import Simplex.Chat.Types.Util (decodeJSON, encodeJSON) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 467857a86a..8091320717 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -47,6 +47,7 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Protocol import Simplex.Chat.Store (AutoAccept, StoreError, UserContactLink, UserMsgReceiptSettings) import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent (AgentClient) import Simplex.Messaging.Agent.Client (AgentLocks, ProtocolTestFailure) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index b0beccb86b..1393831630 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -28,6 +28,7 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import GHC.Generics import Simplex.Chat.Types +import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, sumTypeJSON) diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 23baa618ab..f2d6553a5a 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,6 +35,7 @@ import Simplex.Chat.Markdown import Simplex.Chat.Messages.CIContent import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON) diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 40b139783b..725cf74cf9 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -29,6 +29,8 @@ import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences +import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 62f8055da6..31d1eb5738 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -41,6 +41,7 @@ import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) import Simplex.Chat.Call import Simplex.Chat.Types +import Simplex.Chat.Types.Util import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 416986173c..4b66291fe9 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -23,6 +23,7 @@ import Simplex.Chat.Store.Groups import Simplex.Chat.Store.Shared import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Store.SQLite (firstRow, firstRow') getConnectionEntity :: DB.Connection -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index da03ed3a7c..944527ae48 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -69,6 +69,7 @@ import qualified Database.SQLite.Simple as DB import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index c622f3e87b..c8b58fa857 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -97,6 +97,7 @@ import Simplex.Chat.Messages import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Crypto as C diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 797f237f79..608befb54d 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -74,6 +74,7 @@ import Simplex.Chat.Protocol import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Crypto as C diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index fc1444a8a9..6f368a774a 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -31,6 +31,7 @@ import GHC.Generics (Generic) import Simplex.Chat.Messages import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 1ddf3c3845..33a6bd0456 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -13,9 +13,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StrictData #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} @@ -24,7 +22,6 @@ module Simplex.Chat.Types where -import Control.Applicative ((<|>)) import Crypto.Number.Serialize (os2ip) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as J @@ -33,27 +30,22 @@ import qualified Data.Aeson.Types as JT import qualified Data.Attoparsec.ByteString.Char8 as A import Data.ByteString.Char8 (ByteString, pack, unpack) import qualified Data.ByteString.Char8 as B -import qualified Data.ByteString.Lazy.Char8 as LB import Data.Int (Int64) -import Data.Maybe (fromMaybe, isJust) +import Data.Maybe (isJust) import Data.Text (Text) import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) import Data.Time.Clock (UTCTime) -import Data.Typeable -import Database.SQLite.Simple (ResultError (..), SQLData (..)) -import Database.SQLite.Simple.FromField (FieldParser, FromField (..), returnError) -import Database.SQLite.Simple.Internal (Field (..)) -import Database.SQLite.Simple.Ok (Ok (Ok)) +import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) -import GHC.Records.Compat +import Simplex.Chat.Types.Preferences +import Simplex.Chat.Types.Util import Simplex.FileTransfer.Description (FileDigest) import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, sumTypeJSON, taggedObjectJSON) +import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI) -import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) +import Simplex.Messaging.Util ((<$?>)) class IsContact a where contactId' :: a -> ContactId @@ -353,659 +345,14 @@ defaultChatSettings = ChatSettings pattern DisableNtfs :: ChatSettings pattern DisableNtfs <- ChatSettings {enableNtfs = False} -data ChatFeature - = CFTimedMessages - | CFFullDelete - | CFReactions - | CFVoice - | CFCalls - deriving (Show, Generic) - -data SChatFeature (f :: ChatFeature) where - SCFTimedMessages :: SChatFeature 'CFTimedMessages - SCFFullDelete :: SChatFeature 'CFFullDelete - SCFReactions :: SChatFeature 'CFReactions - SCFVoice :: SChatFeature 'CFVoice - SCFCalls :: SChatFeature 'CFCalls - -deriving instance Show (SChatFeature f) - -data AChatFeature = forall f. FeatureI f => ACF (SChatFeature f) - -deriving instance Show AChatFeature - -chatFeatureNameText :: ChatFeature -> Text -chatFeatureNameText = \case - CFTimedMessages -> "Disappearing messages" - CFFullDelete -> "Full deletion" - CFReactions -> "Message reactions" - CFVoice -> "Voice messages" - CFCalls -> "Audio/video calls" - -chatFeatureNameText' :: SChatFeature f -> Text -chatFeatureNameText' = chatFeatureNameText . chatFeature - featureAllowed :: SChatFeature f -> (PrefEnabled -> Bool) -> Contact -> Bool featureAllowed feature forWhom Contact {mergedPreferences} = let ContactUserPreference {enabled} = getContactUserPreference feature mergedPreferences in forWhom enabled -instance ToJSON ChatFeature where - toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CF" - toJSON = J.genericToJSON . enumJSON $ dropPrefix "CF" - -instance FromJSON ChatFeature where - parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CF" - -allChatFeatures :: [AChatFeature] -allChatFeatures = - [ ACF SCFTimedMessages, - ACF SCFFullDelete, - ACF SCFReactions, - ACF SCFVoice, - ACF SCFCalls - ] - -chatPrefSel :: SChatFeature f -> Preferences -> Maybe (FeaturePreference f) -chatPrefSel = \case - SCFTimedMessages -> timedMessages - SCFFullDelete -> fullDelete - SCFReactions -> reactions - SCFVoice -> voice - SCFCalls -> calls - -chatFeature :: SChatFeature f -> ChatFeature -chatFeature = \case - SCFTimedMessages -> CFTimedMessages - SCFFullDelete -> CFFullDelete - SCFReactions -> CFReactions - SCFVoice -> CFVoice - SCFCalls -> CFCalls - -class PreferenceI p where - getPreference :: SChatFeature f -> p -> FeaturePreference f - -instance PreferenceI Preferences where - getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f prefs) - -instance PreferenceI (Maybe Preferences) where - getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f =<< prefs) - -instance PreferenceI FullPreferences where - getPreference = \case - SCFTimedMessages -> timedMessages - SCFFullDelete -> fullDelete - SCFReactions -> reactions - SCFVoice -> voice - SCFCalls -> calls - {-# INLINE getPreference #-} - -setPreference :: forall f. FeatureI f => SChatFeature f -> Maybe FeatureAllowed -> Maybe Preferences -> Preferences -setPreference f allow_ prefs_ = setPreference_ f pref $ fromMaybe emptyChatPrefs prefs_ - where - pref = setAllow <$> allow_ - setAllow :: FeatureAllowed -> FeaturePreference f - setAllow = setField @"allow" (getPreference f prefs) - prefs = mergePreferences Nothing prefs_ - -setPreference' :: SChatFeature f -> Maybe (FeaturePreference f) -> Maybe Preferences -> Preferences -setPreference' f pref_ prefs_ = setPreference_ f pref_ $ fromMaybe emptyChatPrefs prefs_ - -setPreference_ :: SChatFeature f -> Maybe (FeaturePreference f) -> Preferences -> Preferences -setPreference_ f pref_ prefs = - case f of - SCFTimedMessages -> prefs {timedMessages = pref_} - SCFFullDelete -> prefs {fullDelete = pref_} - SCFReactions -> prefs {reactions = pref_} - SCFVoice -> prefs {voice = pref_} - SCFCalls -> prefs {calls = pref_} - --- collection of optional chat preferences for the user and the contact -data Preferences = Preferences - { timedMessages :: Maybe TimedMessagesPreference, - fullDelete :: Maybe FullDeletePreference, - reactions :: Maybe ReactionsPreference, - voice :: Maybe VoicePreference, - calls :: Maybe CallsPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON Preferences where - toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} - toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} - -instance ToField Preferences where - toField = toField . encodeJSON - -instance FromField Preferences where - fromField = fromTextField_ decodeJSON - -data GroupFeature - = GFTimedMessages - | GFDirectMessages - | GFFullDelete - | GFReactions - | GFVoice - | GFFiles - deriving (Show, Generic) - -data SGroupFeature (f :: GroupFeature) where - SGFTimedMessages :: SGroupFeature 'GFTimedMessages - SGFDirectMessages :: SGroupFeature 'GFDirectMessages - SGFFullDelete :: SGroupFeature 'GFFullDelete - SGFReactions :: SGroupFeature 'GFReactions - SGFVoice :: SGroupFeature 'GFVoice - SGFFiles :: SGroupFeature 'GFFiles - -deriving instance Show (SGroupFeature f) - -data AGroupFeature = forall f. GroupFeatureI f => AGF (SGroupFeature f) - -deriving instance Show AGroupFeature - -groupFeatureNameText :: GroupFeature -> Text -groupFeatureNameText = \case - GFTimedMessages -> "Disappearing messages" - GFDirectMessages -> "Direct messages" - GFFullDelete -> "Full deletion" - GFReactions -> "Message reactions" - GFVoice -> "Voice messages" - GFFiles -> "Files and media" - -groupFeatureNameText' :: SGroupFeature f -> Text -groupFeatureNameText' = groupFeatureNameText . toGroupFeature - groupFeatureAllowed :: GroupFeatureI f => SGroupFeature f -> GroupInfo -> Bool groupFeatureAllowed feature gInfo = groupFeatureAllowed' feature $ fullGroupPreferences gInfo -groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferences -> Bool -groupFeatureAllowed' feature prefs = - getField @"enable" (getGroupPreference feature prefs) == FEOn - -instance ToJSON GroupFeature where - toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF" - toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF" - -instance FromJSON GroupFeature where - parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF" - -allGroupFeatures :: [AGroupFeature] -allGroupFeatures = - [ AGF SGFTimedMessages, - AGF SGFDirectMessages, - AGF SGFFullDelete, - AGF SGFReactions, - AGF SGFVoice, - AGF SGFFiles - ] - -groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) -groupPrefSel = \case - SGFTimedMessages -> timedMessages - SGFDirectMessages -> directMessages - SGFFullDelete -> fullDelete - SGFReactions -> reactions - SGFVoice -> voice - SGFFiles -> files - -toGroupFeature :: SGroupFeature f -> GroupFeature -toGroupFeature = \case - SGFTimedMessages -> GFTimedMessages - SGFDirectMessages -> GFDirectMessages - SGFFullDelete -> GFFullDelete - SGFReactions -> GFReactions - SGFVoice -> GFVoice - SGFFiles -> GFFiles - -class GroupPreferenceI p where - getGroupPreference :: SGroupFeature f -> p -> GroupFeaturePreference f - -instance GroupPreferenceI GroupPreferences where - getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs) - -instance GroupPreferenceI (Maybe GroupPreferences) where - getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) - -instance GroupPreferenceI FullGroupPreferences where - getGroupPreference = \case - SGFTimedMessages -> timedMessages - SGFDirectMessages -> directMessages - SGFFullDelete -> fullDelete - SGFReactions -> reactions - SGFVoice -> voice - SGFFiles -> files - {-# INLINE getGroupPreference #-} - --- collection of optional group preferences -data GroupPreferences = GroupPreferences - { timedMessages :: Maybe TimedMessagesGroupPreference, - directMessages :: Maybe DirectMessagesGroupPreference, - fullDelete :: Maybe FullDeleteGroupPreference, - reactions :: Maybe ReactionsGroupPreference, - voice :: Maybe VoiceGroupPreference, - files :: Maybe FilesGroupPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON GroupPreferences where - toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} - toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} - -instance ToField GroupPreferences where - toField = toField . encodeJSON - -instance FromField GroupPreferences where - fromField = fromTextField_ decodeJSON - -setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences -setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs - where - prefs = mergeGroupPreferences prefs_ - pref :: GroupFeaturePreference f - pref = setField @"enable" (getGroupPreference f prefs) enable - -setGroupPreference' :: SGroupFeature f -> GroupFeaturePreference f -> Maybe GroupPreferences -> GroupPreferences -setGroupPreference' f pref prefs_ = setGroupPreference_ f pref prefs - where - prefs = mergeGroupPreferences prefs_ - -setGroupPreference_ :: SGroupFeature f -> GroupFeaturePreference f -> FullGroupPreferences -> GroupPreferences -setGroupPreference_ f pref prefs = - toGroupPreferences $ case f of - SGFTimedMessages -> prefs {timedMessages = pref} - SGFDirectMessages -> prefs {directMessages = pref} - SGFFullDelete -> prefs {fullDelete = pref} - SGFReactions -> prefs {reactions = pref} - SGFVoice -> prefs {voice = pref} - SGFFiles -> prefs {files = pref} - -setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences -setGroupTimedMessagesPreference pref prefs_ = - toGroupPreferences $ prefs {timedMessages = pref} - where - prefs = mergeGroupPreferences prefs_ - --- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing --- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here. -data FullPreferences = FullPreferences - { timedMessages :: TimedMessagesPreference, - fullDelete :: FullDeletePreference, - reactions :: ReactionsPreference, - voice :: VoicePreference, - calls :: CallsPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.defaultOptions - --- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing --- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here. -data FullGroupPreferences = FullGroupPreferences - { timedMessages :: TimedMessagesGroupPreference, - directMessages :: DirectMessagesGroupPreference, - fullDelete :: FullDeleteGroupPreference, - reactions :: ReactionsGroupPreference, - voice :: VoiceGroupPreference, - files :: FilesGroupPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON FullGroupPreferences where toEncoding = J.genericToEncoding J.defaultOptions - --- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences -data ContactUserPreferences = ContactUserPreferences - { timedMessages :: ContactUserPreference TimedMessagesPreference, - fullDelete :: ContactUserPreference FullDeletePreference, - reactions :: ContactUserPreference ReactionsPreference, - voice :: ContactUserPreference VoicePreference, - calls :: ContactUserPreference CallsPreference - } - deriving (Eq, Show, Generic) - -data ContactUserPreference p = ContactUserPreference - { enabled :: PrefEnabled, - userPreference :: ContactUserPref p, - contactPreference :: p - } - deriving (Eq, Show, Generic) - -data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p} - deriving (Eq, Show, Generic) - -instance ToJSON ContactUserPreferences where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON p => ToJSON (ContactUserPreference p) where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON p => ToJSON (ContactUserPref p) where - toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CUP" - toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CUP" - -toChatPrefs :: FullPreferences -> Preferences -toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} = - Preferences - { timedMessages = Just timedMessages, - fullDelete = Just fullDelete, - reactions = Just reactions, - voice = Just voice, - calls = Just calls - } - -defaultChatPrefs :: FullPreferences -defaultChatPrefs = - FullPreferences - { timedMessages = TimedMessagesPreference {allow = FAYes, ttl = Nothing}, - fullDelete = FullDeletePreference {allow = FANo}, - reactions = ReactionsPreference {allow = FAYes}, - voice = VoicePreference {allow = FAYes}, - calls = CallsPreference {allow = FAYes} - } - -emptyChatPrefs :: Preferences -emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing - -defaultGroupPrefs :: FullGroupPreferences -defaultGroupPrefs = - FullGroupPreferences - { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400}, - directMessages = DirectMessagesGroupPreference {enable = FEOff}, - fullDelete = FullDeleteGroupPreference {enable = FEOff}, - reactions = ReactionsGroupPreference {enable = FEOn}, - voice = VoiceGroupPreference {enable = FEOn}, - files = FilesGroupPreference {enable = FEOn} - } - -emptyGroupPrefs :: GroupPreferences -emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing - -data TimedMessagesPreference = TimedMessagesPreference - { allow :: FeatureAllowed, - ttl :: Maybe Int - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON TimedMessagesPreference where - toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} - toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} - -data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON FullDeletePreference where toEncoding = J.genericToEncoding J.defaultOptions - -data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON ReactionsPreference where toEncoding = J.genericToEncoding J.defaultOptions - -data VoicePreference = VoicePreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON VoicePreference where toEncoding = J.genericToEncoding J.defaultOptions - -data CallsPreference = CallsPreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON CallsPreference where toEncoding = J.genericToEncoding J.defaultOptions - -class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where - type FeaturePreference (f :: ChatFeature) = p | p -> f - sFeature :: SChatFeature f - prefParam :: FeaturePreference f -> Maybe Int - -instance HasField "allow" TimedMessagesPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: TimedMessagesPreference)) - -instance HasField "allow" FullDeletePreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: FullDeletePreference)) - -instance HasField "allow" ReactionsPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: ReactionsPreference)) - -instance HasField "allow" VoicePreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: VoicePreference)) - -instance HasField "allow" CallsPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: CallsPreference)) - -instance FeatureI 'CFTimedMessages where - type FeaturePreference 'CFTimedMessages = TimedMessagesPreference - sFeature = SCFTimedMessages - prefParam TimedMessagesPreference {ttl} = ttl - -instance FeatureI 'CFFullDelete where - type FeaturePreference 'CFFullDelete = FullDeletePreference - sFeature = SCFFullDelete - prefParam _ = Nothing - -instance FeatureI 'CFReactions where - type FeaturePreference 'CFReactions = ReactionsPreference - sFeature = SCFReactions - prefParam _ = Nothing - -instance FeatureI 'CFVoice where - type FeaturePreference 'CFVoice = VoicePreference - sFeature = SCFVoice - prefParam _ = Nothing - -instance FeatureI 'CFCalls where - type FeaturePreference 'CFCalls = CallsPreference - sFeature = SCFCalls - prefParam _ = Nothing - -data GroupPreference = GroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data TimedMessagesGroupPreference = TimedMessagesGroupPreference - { enable :: GroupFeatureEnabled, - ttl :: Maybe Int - } - deriving (Eq, Show, Generic, FromJSON) - -data DirectMessagesGroupPreference = DirectMessagesGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data FullDeleteGroupPreference = FullDeleteGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data ReactionsGroupPreference = ReactionsGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data VoiceGroupPreference = VoiceGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data FilesGroupPreference = FilesGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON GroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON TimedMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON DirectMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON ReactionsGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON FullDeleteGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON VoiceGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON FilesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where - type GroupFeaturePreference (f :: GroupFeature) = p | p -> f - sGroupFeature :: SGroupFeature f - groupPrefParam :: GroupFeaturePreference f -> Maybe Int - -instance HasField "enable" GroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: GroupPreference)) - -instance HasField "enable" TimedMessagesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: TimedMessagesGroupPreference)) - -instance HasField "enable" DirectMessagesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: DirectMessagesGroupPreference)) - -instance HasField "enable" ReactionsGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: ReactionsGroupPreference)) - -instance HasField "enable" FullDeleteGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: FullDeleteGroupPreference)) - -instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: VoiceGroupPreference)) - -instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: FilesGroupPreference)) - -instance GroupFeatureI 'GFTimedMessages where - type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference - sGroupFeature = SGFTimedMessages - groupPrefParam TimedMessagesGroupPreference {ttl} = ttl - -instance GroupFeatureI 'GFDirectMessages where - type GroupFeaturePreference 'GFDirectMessages = DirectMessagesGroupPreference - sGroupFeature = SGFDirectMessages - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFFullDelete where - type GroupFeaturePreference 'GFFullDelete = FullDeleteGroupPreference - sGroupFeature = SGFFullDelete - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFReactions where - type GroupFeaturePreference 'GFReactions = ReactionsGroupPreference - sGroupFeature = SGFReactions - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFVoice where - type GroupFeaturePreference 'GFVoice = VoiceGroupPreference - sGroupFeature = SGFVoice - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFFiles where - type GroupFeaturePreference 'GFFiles = FilesGroupPreference - sGroupFeature = SGFFiles - groupPrefParam _ = Nothing - -groupPrefStateText :: HasField "enable" p GroupFeatureEnabled => GroupFeature -> p -> Maybe Int -> Text -groupPrefStateText feature pref param = - let enabled = getField @"enable" pref - paramText = if enabled == FEOn then groupParamText_ feature param else "" - in groupFeatureNameText feature <> ": " <> safeDecodeUtf8 (strEncode enabled) <> paramText - -groupParamText_ :: GroupFeature -> Maybe Int -> Text -groupParamText_ feature param = case feature of - GFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param - _ -> "" - -groupPreferenceText :: forall f. GroupFeatureI f => GroupFeaturePreference f -> Text -groupPreferenceText pref = - let feature = toGroupFeature $ sGroupFeature @f - in groupPrefStateText feature pref $ groupPrefParam pref - -timedTTLText :: Int -> Text -timedTTLText 0 = "0 sec" -timedTTLText ttl = do - let (m', s) = ttl `quotRem` 60 - (h', m) = m' `quotRem` 60 - (d', h) = h' `quotRem` 24 - (mm, d) = d' `quotRem` 30 - T.pack . unwords $ - [mms mm | mm /= 0] <> [ds d | d /= 0] <> [hs h | h /= 0] <> [ms m | m /= 0] <> [ss s | s /= 0] - where - ss s = show s <> " sec" - ms m = show m <> " min" - hs 1 = "1 hour" - hs h = show h <> " hours" - ds 1 = "1 day" - ds 7 = "1 week" - ds 14 = "2 weeks" - ds d = show d <> " days" - mms 1 = "1 month" - mms mm = show mm <> " months" - -toGroupPreference :: GroupFeatureI f => GroupFeaturePreference f -> GroupPreference -toGroupPreference p = GroupPreference {enable = getField @"enable" p} - -data FeatureAllowed - = FAAlways -- allow unconditionally - | FAYes -- allow, if peer allows it - | FANo -- do not allow - deriving (Eq, Show, Generic) - -instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode - -instance ToField FeatureAllowed where toField = toField . strEncode - -instance StrEncoding FeatureAllowed where - strEncode = \case - FAAlways -> "always" - FAYes -> "yes" - FANo -> "no" - strDecode = \case - "always" -> Right FAAlways - "yes" -> Right FAYes - "no" -> Right FANo - r -> Left $ "bad FeatureAllowed " <> B.unpack r - strP = strDecode <$?> A.takeByteString - -instance FromJSON FeatureAllowed where - parseJSON = strParseJSON "FeatureAllowed" - -instance ToJSON FeatureAllowed where - toJSON = strToJSON - toEncoding = strToJEncoding - -data GroupFeatureEnabled = FEOn | FEOff - deriving (Eq, Show, Generic) - -instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode - -instance ToField GroupFeatureEnabled where toField = toField . strEncode - -instance StrEncoding GroupFeatureEnabled where - strEncode = \case - FEOn -> "on" - FEOff -> "off" - strDecode = \case - "on" -> Right FEOn - "off" -> Right FEOff - r -> Left $ "bad GroupFeatureEnabled " <> B.unpack r - strP = strDecode <$?> A.takeByteString - -instance FromJSON GroupFeatureEnabled where - parseJSON = strParseJSON "GroupFeatureEnabled" - -instance ToJSON GroupFeatureEnabled where - toJSON = strToJSON - toEncoding = strToJEncoding - -groupFeatureState :: GroupFeatureI f => GroupFeaturePreference f -> (GroupFeatureEnabled, Maybe Int) -groupFeatureState p = - let enable = getField @"enable" p - param = if enable == FEOn then groupPrefParam p else Nothing - in (enable, param) - -mergePreferences :: Maybe Preferences -> Maybe Preferences -> FullPreferences -mergePreferences contactPrefs userPreferences = - FullPreferences - { timedMessages = pref SCFTimedMessages, - fullDelete = pref SCFFullDelete, - reactions = pref SCFReactions, - voice = pref SCFVoice, - calls = pref SCFCalls - } - where - pref :: SChatFeature f -> FeaturePreference f - pref f = - let sel = chatPrefSel f - in fromMaybe (getPreference f defaultChatPrefs) $ (contactPrefs >>= sel) <|> (userPreferences >>= sel) - mergeUserChatPrefs :: User -> Contact -> FullPreferences mergeUserChatPrefs user ct = mergeUserChatPrefs' user (contactConnIncognito ct) (userPreferences ct) @@ -1014,94 +361,6 @@ mergeUserChatPrefs' user connectedIncognito userPreferences = let userPrefs = if connectedIncognito then Nothing else preferences' user in mergePreferences (Just userPreferences) userPrefs -mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences -mergeGroupPreferences groupPreferences = - FullGroupPreferences - { timedMessages = pref SGFTimedMessages, - directMessages = pref SGFDirectMessages, - fullDelete = pref SGFFullDelete, - reactions = pref SGFReactions, - voice = pref SGFVoice, - files = pref SGFFiles - } - where - pref :: SGroupFeature f -> GroupFeaturePreference f - pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt) - -toGroupPreferences :: FullGroupPreferences -> GroupPreferences -toGroupPreferences groupPreferences = - GroupPreferences - { timedMessages = pref SGFTimedMessages, - directMessages = pref SGFDirectMessages, - fullDelete = pref SGFFullDelete, - reactions = pref SGFReactions, - voice = pref SGFVoice, - files = pref SGFFiles - } - where - pref :: SGroupFeature f -> Maybe (GroupFeaturePreference f) - pref f = Just $ getGroupPreference f groupPreferences - -data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON PrefEnabled where - toJSON = J.genericToJSON J.defaultOptions - toEncoding = J.genericToEncoding J.defaultOptions - -prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled -prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of - (FAAlways, FANo) -> PrefEnabled {forUser = False, forContact = asymmetric} - (FANo, FAAlways) -> PrefEnabled {forUser = asymmetric, forContact = False} - (_, FANo) -> PrefEnabled False False - (FANo, _) -> PrefEnabled False False - _ -> PrefEnabled True True - -prefStateText :: ChatFeature -> FeatureAllowed -> Maybe Int -> Text -prefStateText feature allowed param = case allowed of - FANo -> "cancelled " <> chatFeatureNameText feature - _ -> "offered " <> chatFeatureNameText feature <> paramText_ feature param - -featureStateText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text -featureStateText feature enabled param = - chatFeatureNameText feature <> ": " <> prefEnabledToText feature enabled param <> case enabled of - PrefEnabled {forUser = True} -> paramText_ feature param - _ -> "" - -paramText_ :: ChatFeature -> Maybe Int -> Text -paramText_ feature param = case feature of - CFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param - _ -> "" - -prefEnabledToText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text -prefEnabledToText f enabled param = case enabled of - PrefEnabled True True -> enabledStr - PrefEnabled False False -> "off" - PrefEnabled {forUser = True, forContact = False} -> enabledStr <> " for you" - PrefEnabled {forUser = False, forContact = True} -> enabledStr <> " for contact" - where - enabledStr = case f of - CFTimedMessages -> if isJust param then "enabled" else "allowed" - _ -> "enabled" - -preferenceText :: forall f. FeatureI f => FeaturePreference f -> Text -preferenceText p = - let feature = chatFeature $ sFeature @f - allowed = getField @"allow" p - paramText = if allowed == FAAlways || allowed == FAYes then paramText_ feature (prefParam p) else "" - in safeDecodeUtf8 (strEncode allowed) <> paramText - -featureState :: FeatureI f => ContactUserPreference (FeaturePreference f) -> (PrefEnabled, Maybe Int) -featureState ContactUserPreference {enabled, userPreference} = - let param = if forUser enabled then prefParam $ preference userPreference else Nothing - in (enabled, param) - -preferenceState :: FeatureI f => FeaturePreference f -> (FeatureAllowed, Maybe Int) -preferenceState pref = - let allow = getField @"allow" pref - param = if allow == FAAlways || allow == FAYes then prefParam pref else Nothing - in (allow, param) - updateMergedPreferences :: User -> Contact -> Contact updateMergedPreferences user ct = let mergedPreferences = contactUserPreferences user (userPreferences ct) (preferences' ct) (contactConnIncognito ct) @@ -1135,14 +394,6 @@ contactUserPreferences user userPreferences contactPreferences connectedIncognit ctUserPrefs = mergeUserChatPrefs' user connectedIncognito userPreferences ctPrefs = mergePreferences contactPreferences Nothing -getContactUserPreference :: SChatFeature f -> ContactUserPreferences -> ContactUserPreference (FeaturePreference f) -getContactUserPreference = \case - SCFTimedMessages -> timedMessages - SCFFullDelete -> fullDelete - SCFReactions -> reactions - SCFVoice -> voice - SCFCalls -> calls - data Profile = Profile { displayName :: ContactName, fullName :: Text, @@ -1433,14 +684,6 @@ instance ToJSON GroupMemberRole where toJSON = strToJSON toEncoding = strToJEncoding -fromBlobField_ :: Typeable k => (ByteString -> Either String k) -> FieldParser k -fromBlobField_ p = \case - f@(Field (SQLBlob b) _) -> - case p b of - Right k -> Ok k - Left e -> returnError ConversionFailed f ("could not parse field: " ++ e) - f -> returnError ConversionFailed f "expecting SQLBlob column type" - newtype Probe = Probe {unProbe :: ByteString} deriving (Eq, Show) @@ -2194,12 +1437,6 @@ data XGrpMemIntroCont = XGrpMemIntroCont } deriving (Show) -encodeJSON :: ToJSON a => a -> Text -encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode - -decodeJSON :: FromJSON a => Text -> Maybe a -decodeJSON = J.decode . LB.fromStrict . encodeUtf8 - data ServerCfg p = ServerCfg { server :: ProtoServerWithAuth p, preset :: Bool, diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs new file mode 100644 index 0000000000..a89e383242 --- /dev/null +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -0,0 +1,778 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilyDependencies #-} +{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} + +{-# HLINT ignore "Use newtype instead of data" #-} + +module Simplex.Chat.Types.Preferences where + +import Control.Applicative ((<|>)) +import Data.Aeson (FromJSON (..), ToJSON (..)) +import qualified Data.Aeson as J +import qualified Data.Attoparsec.ByteString.Char8 as A +import qualified Data.ByteString.Char8 as B +import Data.Maybe (fromMaybe, isJust) +import Data.Text (Text) +import qualified Data.Text as T +import Database.SQLite.Simple.FromField (FromField (..)) +import Database.SQLite.Simple.ToField (ToField (..)) +import GHC.Generics (Generic) +import GHC.Records.Compat +import Simplex.Chat.Types.Util +import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) + +data ChatFeature + = CFTimedMessages + | CFFullDelete + | CFReactions + | CFVoice + | CFCalls + deriving (Show, Generic) + +data SChatFeature (f :: ChatFeature) where + SCFTimedMessages :: SChatFeature 'CFTimedMessages + SCFFullDelete :: SChatFeature 'CFFullDelete + SCFReactions :: SChatFeature 'CFReactions + SCFVoice :: SChatFeature 'CFVoice + SCFCalls :: SChatFeature 'CFCalls + +deriving instance Show (SChatFeature f) + +data AChatFeature = forall f. FeatureI f => ACF (SChatFeature f) + +deriving instance Show AChatFeature + +chatFeatureNameText :: ChatFeature -> Text +chatFeatureNameText = \case + CFTimedMessages -> "Disappearing messages" + CFFullDelete -> "Full deletion" + CFReactions -> "Message reactions" + CFVoice -> "Voice messages" + CFCalls -> "Audio/video calls" + +chatFeatureNameText' :: SChatFeature f -> Text +chatFeatureNameText' = chatFeatureNameText . chatFeature + +instance ToJSON ChatFeature where + toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CF" + toJSON = J.genericToJSON . enumJSON $ dropPrefix "CF" + +instance FromJSON ChatFeature where + parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CF" + +allChatFeatures :: [AChatFeature] +allChatFeatures = + [ ACF SCFTimedMessages, + ACF SCFFullDelete, + ACF SCFReactions, + ACF SCFVoice, + ACF SCFCalls + ] + +chatPrefSel :: SChatFeature f -> Preferences -> Maybe (FeaturePreference f) +chatPrefSel = \case + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls + +chatFeature :: SChatFeature f -> ChatFeature +chatFeature = \case + SCFTimedMessages -> CFTimedMessages + SCFFullDelete -> CFFullDelete + SCFReactions -> CFReactions + SCFVoice -> CFVoice + SCFCalls -> CFCalls + +class PreferenceI p where + getPreference :: SChatFeature f -> p -> FeaturePreference f + +instance PreferenceI Preferences where + getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f prefs) + +instance PreferenceI (Maybe Preferences) where + getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f =<< prefs) + +instance PreferenceI FullPreferences where + getPreference = \case + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls + {-# INLINE getPreference #-} + +setPreference :: forall f. FeatureI f => SChatFeature f -> Maybe FeatureAllowed -> Maybe Preferences -> Preferences +setPreference f allow_ prefs_ = setPreference_ f pref $ fromMaybe emptyChatPrefs prefs_ + where + pref = setAllow <$> allow_ + setAllow :: FeatureAllowed -> FeaturePreference f + setAllow = setField @"allow" (getPreference f prefs) + prefs = mergePreferences Nothing prefs_ + +setPreference' :: SChatFeature f -> Maybe (FeaturePreference f) -> Maybe Preferences -> Preferences +setPreference' f pref_ prefs_ = setPreference_ f pref_ $ fromMaybe emptyChatPrefs prefs_ + +setPreference_ :: SChatFeature f -> Maybe (FeaturePreference f) -> Preferences -> Preferences +setPreference_ f pref_ prefs = + case f of + SCFTimedMessages -> prefs {timedMessages = pref_} + SCFFullDelete -> prefs {fullDelete = pref_} + SCFReactions -> prefs {reactions = pref_} + SCFVoice -> prefs {voice = pref_} + SCFCalls -> prefs {calls = pref_} + +-- collection of optional chat preferences for the user and the contact +data Preferences = Preferences + { timedMessages :: Maybe TimedMessagesPreference, + fullDelete :: Maybe FullDeletePreference, + reactions :: Maybe ReactionsPreference, + voice :: Maybe VoicePreference, + calls :: Maybe CallsPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON Preferences where + toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} + toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} + +instance ToField Preferences where + toField = toField . encodeJSON + +instance FromField Preferences where + fromField = fromTextField_ decodeJSON + +data GroupFeature + = GFTimedMessages + | GFDirectMessages + | GFFullDelete + | GFReactions + | GFVoice + | GFFiles + deriving (Show, Generic) + +data SGroupFeature (f :: GroupFeature) where + SGFTimedMessages :: SGroupFeature 'GFTimedMessages + SGFDirectMessages :: SGroupFeature 'GFDirectMessages + SGFFullDelete :: SGroupFeature 'GFFullDelete + SGFReactions :: SGroupFeature 'GFReactions + SGFVoice :: SGroupFeature 'GFVoice + SGFFiles :: SGroupFeature 'GFFiles + +deriving instance Show (SGroupFeature f) + +data AGroupFeature = forall f. GroupFeatureI f => AGF (SGroupFeature f) + +deriving instance Show AGroupFeature + +groupFeatureNameText :: GroupFeature -> Text +groupFeatureNameText = \case + GFTimedMessages -> "Disappearing messages" + GFDirectMessages -> "Direct messages" + GFFullDelete -> "Full deletion" + GFReactions -> "Message reactions" + GFVoice -> "Voice messages" + GFFiles -> "Files and media" + +groupFeatureNameText' :: SGroupFeature f -> Text +groupFeatureNameText' = groupFeatureNameText . toGroupFeature + +groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferences -> Bool +groupFeatureAllowed' feature prefs = + getField @"enable" (getGroupPreference feature prefs) == FEOn + +instance ToJSON GroupFeature where + toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF" + toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF" + +instance FromJSON GroupFeature where + parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF" + +allGroupFeatures :: [AGroupFeature] +allGroupFeatures = + [ AGF SGFTimedMessages, + AGF SGFDirectMessages, + AGF SGFFullDelete, + AGF SGFReactions, + AGF SGFVoice, + AGF SGFFiles + ] + +groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) +groupPrefSel = \case + SGFTimedMessages -> timedMessages + SGFDirectMessages -> directMessages + SGFFullDelete -> fullDelete + SGFReactions -> reactions + SGFVoice -> voice + SGFFiles -> files + +toGroupFeature :: SGroupFeature f -> GroupFeature +toGroupFeature = \case + SGFTimedMessages -> GFTimedMessages + SGFDirectMessages -> GFDirectMessages + SGFFullDelete -> GFFullDelete + SGFReactions -> GFReactions + SGFVoice -> GFVoice + SGFFiles -> GFFiles + +class GroupPreferenceI p where + getGroupPreference :: SGroupFeature f -> p -> GroupFeaturePreference f + +instance GroupPreferenceI GroupPreferences where + getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs) + +instance GroupPreferenceI (Maybe GroupPreferences) where + getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) + +instance GroupPreferenceI FullGroupPreferences where + getGroupPreference = \case + SGFTimedMessages -> timedMessages + SGFDirectMessages -> directMessages + SGFFullDelete -> fullDelete + SGFReactions -> reactions + SGFVoice -> voice + SGFFiles -> files + {-# INLINE getGroupPreference #-} + +-- collection of optional group preferences +data GroupPreferences = GroupPreferences + { timedMessages :: Maybe TimedMessagesGroupPreference, + directMessages :: Maybe DirectMessagesGroupPreference, + fullDelete :: Maybe FullDeleteGroupPreference, + reactions :: Maybe ReactionsGroupPreference, + voice :: Maybe VoiceGroupPreference, + files :: Maybe FilesGroupPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON GroupPreferences where + toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} + toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} + +instance ToField GroupPreferences where + toField = toField . encodeJSON + +instance FromField GroupPreferences where + fromField = fromTextField_ decodeJSON + +setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences +setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs + where + prefs = mergeGroupPreferences prefs_ + pref :: GroupFeaturePreference f + pref = setField @"enable" (getGroupPreference f prefs) enable + +setGroupPreference' :: SGroupFeature f -> GroupFeaturePreference f -> Maybe GroupPreferences -> GroupPreferences +setGroupPreference' f pref prefs_ = setGroupPreference_ f pref prefs + where + prefs = mergeGroupPreferences prefs_ + +setGroupPreference_ :: SGroupFeature f -> GroupFeaturePreference f -> FullGroupPreferences -> GroupPreferences +setGroupPreference_ f pref prefs = + toGroupPreferences $ case f of + SGFTimedMessages -> prefs {timedMessages = pref} + SGFDirectMessages -> prefs {directMessages = pref} + SGFFullDelete -> prefs {fullDelete = pref} + SGFReactions -> prefs {reactions = pref} + SGFVoice -> prefs {voice = pref} + SGFFiles -> prefs {files = pref} + +setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences +setGroupTimedMessagesPreference pref prefs_ = + toGroupPreferences $ prefs {timedMessages = pref} + where + prefs = mergeGroupPreferences prefs_ + +-- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing +-- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here. +data FullPreferences = FullPreferences + { timedMessages :: TimedMessagesPreference, + fullDelete :: FullDeletePreference, + reactions :: ReactionsPreference, + voice :: VoicePreference, + calls :: CallsPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.defaultOptions + +-- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing +-- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here. +data FullGroupPreferences = FullGroupPreferences + { timedMessages :: TimedMessagesGroupPreference, + directMessages :: DirectMessagesGroupPreference, + fullDelete :: FullDeleteGroupPreference, + reactions :: ReactionsGroupPreference, + voice :: VoiceGroupPreference, + files :: FilesGroupPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON FullGroupPreferences where toEncoding = J.genericToEncoding J.defaultOptions + +-- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences +data ContactUserPreferences = ContactUserPreferences + { timedMessages :: ContactUserPreference TimedMessagesPreference, + fullDelete :: ContactUserPreference FullDeletePreference, + reactions :: ContactUserPreference ReactionsPreference, + voice :: ContactUserPreference VoicePreference, + calls :: ContactUserPreference CallsPreference + } + deriving (Eq, Show, Generic) + +data ContactUserPreference p = ContactUserPreference + { enabled :: PrefEnabled, + userPreference :: ContactUserPref p, + contactPreference :: p + } + deriving (Eq, Show, Generic) + +data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p} + deriving (Eq, Show, Generic) + +instance ToJSON ContactUserPreferences where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON p => ToJSON (ContactUserPreference p) where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON p => ToJSON (ContactUserPref p) where + toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CUP" + toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CUP" + +toChatPrefs :: FullPreferences -> Preferences +toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} = + Preferences + { timedMessages = Just timedMessages, + fullDelete = Just fullDelete, + reactions = Just reactions, + voice = Just voice, + calls = Just calls + } + +defaultChatPrefs :: FullPreferences +defaultChatPrefs = + FullPreferences + { timedMessages = TimedMessagesPreference {allow = FAYes, ttl = Nothing}, + fullDelete = FullDeletePreference {allow = FANo}, + reactions = ReactionsPreference {allow = FAYes}, + voice = VoicePreference {allow = FAYes}, + calls = CallsPreference {allow = FAYes} + } + +emptyChatPrefs :: Preferences +emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing + +defaultGroupPrefs :: FullGroupPreferences +defaultGroupPrefs = + FullGroupPreferences + { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400}, + directMessages = DirectMessagesGroupPreference {enable = FEOff}, + fullDelete = FullDeleteGroupPreference {enable = FEOff}, + reactions = ReactionsGroupPreference {enable = FEOn}, + voice = VoiceGroupPreference {enable = FEOn}, + files = FilesGroupPreference {enable = FEOn} + } + +emptyGroupPrefs :: GroupPreferences +emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing + +data TimedMessagesPreference = TimedMessagesPreference + { allow :: FeatureAllowed, + ttl :: Maybe Int + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON TimedMessagesPreference where + toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} + toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} + +data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON FullDeletePreference where toEncoding = J.genericToEncoding J.defaultOptions + +data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON ReactionsPreference where toEncoding = J.genericToEncoding J.defaultOptions + +data VoicePreference = VoicePreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON VoicePreference where toEncoding = J.genericToEncoding J.defaultOptions + +data CallsPreference = CallsPreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON CallsPreference where toEncoding = J.genericToEncoding J.defaultOptions + +class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where + type FeaturePreference (f :: ChatFeature) = p | p -> f + sFeature :: SChatFeature f + prefParam :: FeaturePreference f -> Maybe Int + +instance HasField "allow" TimedMessagesPreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: TimedMessagesPreference)) + +instance HasField "allow" FullDeletePreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: FullDeletePreference)) + +instance HasField "allow" ReactionsPreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: ReactionsPreference)) + +instance HasField "allow" VoicePreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: VoicePreference)) + +instance HasField "allow" CallsPreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: CallsPreference)) + +instance FeatureI 'CFTimedMessages where + type FeaturePreference 'CFTimedMessages = TimedMessagesPreference + sFeature = SCFTimedMessages + prefParam TimedMessagesPreference {ttl} = ttl + +instance FeatureI 'CFFullDelete where + type FeaturePreference 'CFFullDelete = FullDeletePreference + sFeature = SCFFullDelete + prefParam _ = Nothing + +instance FeatureI 'CFReactions where + type FeaturePreference 'CFReactions = ReactionsPreference + sFeature = SCFReactions + prefParam _ = Nothing + +instance FeatureI 'CFVoice where + type FeaturePreference 'CFVoice = VoicePreference + sFeature = SCFVoice + prefParam _ = Nothing + +instance FeatureI 'CFCalls where + type FeaturePreference 'CFCalls = CallsPreference + sFeature = SCFCalls + prefParam _ = Nothing + +data GroupPreference = GroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data TimedMessagesGroupPreference = TimedMessagesGroupPreference + { enable :: GroupFeatureEnabled, + ttl :: Maybe Int + } + deriving (Eq, Show, Generic, FromJSON) + +data DirectMessagesGroupPreference = DirectMessagesGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data FullDeleteGroupPreference = FullDeleteGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data ReactionsGroupPreference = ReactionsGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data VoiceGroupPreference = VoiceGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data FilesGroupPreference = FilesGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON GroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON TimedMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON DirectMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON ReactionsGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON FullDeleteGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON VoiceGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON FilesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where + type GroupFeaturePreference (f :: GroupFeature) = p | p -> f + sGroupFeature :: SGroupFeature f + groupPrefParam :: GroupFeaturePreference f -> Maybe Int + +instance HasField "enable" GroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: GroupPreference)) + +instance HasField "enable" TimedMessagesGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: TimedMessagesGroupPreference)) + +instance HasField "enable" DirectMessagesGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: DirectMessagesGroupPreference)) + +instance HasField "enable" ReactionsGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: ReactionsGroupPreference)) + +instance HasField "enable" FullDeleteGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: FullDeleteGroupPreference)) + +instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: VoiceGroupPreference)) + +instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: FilesGroupPreference)) + +instance GroupFeatureI 'GFTimedMessages where + type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference + sGroupFeature = SGFTimedMessages + groupPrefParam TimedMessagesGroupPreference {ttl} = ttl + +instance GroupFeatureI 'GFDirectMessages where + type GroupFeaturePreference 'GFDirectMessages = DirectMessagesGroupPreference + sGroupFeature = SGFDirectMessages + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFFullDelete where + type GroupFeaturePreference 'GFFullDelete = FullDeleteGroupPreference + sGroupFeature = SGFFullDelete + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFReactions where + type GroupFeaturePreference 'GFReactions = ReactionsGroupPreference + sGroupFeature = SGFReactions + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFVoice where + type GroupFeaturePreference 'GFVoice = VoiceGroupPreference + sGroupFeature = SGFVoice + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFFiles where + type GroupFeaturePreference 'GFFiles = FilesGroupPreference + sGroupFeature = SGFFiles + groupPrefParam _ = Nothing + +groupPrefStateText :: HasField "enable" p GroupFeatureEnabled => GroupFeature -> p -> Maybe Int -> Text +groupPrefStateText feature pref param = + let enabled = getField @"enable" pref + paramText = if enabled == FEOn then groupParamText_ feature param else "" + in groupFeatureNameText feature <> ": " <> safeDecodeUtf8 (strEncode enabled) <> paramText + +groupParamText_ :: GroupFeature -> Maybe Int -> Text +groupParamText_ feature param = case feature of + GFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param + _ -> "" + +groupPreferenceText :: forall f. GroupFeatureI f => GroupFeaturePreference f -> Text +groupPreferenceText pref = + let feature = toGroupFeature $ sGroupFeature @f + in groupPrefStateText feature pref $ groupPrefParam pref + +timedTTLText :: Int -> Text +timedTTLText 0 = "0 sec" +timedTTLText ttl = do + let (m', s) = ttl `quotRem` 60 + (h', m) = m' `quotRem` 60 + (d', h) = h' `quotRem` 24 + (mm, d) = d' `quotRem` 30 + T.pack . unwords $ + [mms mm | mm /= 0] <> [ds d | d /= 0] <> [hs h | h /= 0] <> [ms m | m /= 0] <> [ss s | s /= 0] + where + ss s = show s <> " sec" + ms m = show m <> " min" + hs 1 = "1 hour" + hs h = show h <> " hours" + ds 1 = "1 day" + ds 7 = "1 week" + ds 14 = "2 weeks" + ds d = show d <> " days" + mms 1 = "1 month" + mms mm = show mm <> " months" + +toGroupPreference :: GroupFeatureI f => GroupFeaturePreference f -> GroupPreference +toGroupPreference p = GroupPreference {enable = getField @"enable" p} + +data FeatureAllowed + = FAAlways -- allow unconditionally + | FAYes -- allow, if peer allows it + | FANo -- do not allow + deriving (Eq, Show, Generic) + +instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode + +instance ToField FeatureAllowed where toField = toField . strEncode + +instance StrEncoding FeatureAllowed where + strEncode = \case + FAAlways -> "always" + FAYes -> "yes" + FANo -> "no" + strDecode = \case + "always" -> Right FAAlways + "yes" -> Right FAYes + "no" -> Right FANo + r -> Left $ "bad FeatureAllowed " <> B.unpack r + strP = strDecode <$?> A.takeByteString + +instance FromJSON FeatureAllowed where + parseJSON = strParseJSON "FeatureAllowed" + +instance ToJSON FeatureAllowed where + toJSON = strToJSON + toEncoding = strToJEncoding + +data GroupFeatureEnabled = FEOn | FEOff + deriving (Eq, Show, Generic) + +instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode + +instance ToField GroupFeatureEnabled where toField = toField . strEncode + +instance StrEncoding GroupFeatureEnabled where + strEncode = \case + FEOn -> "on" + FEOff -> "off" + strDecode = \case + "on" -> Right FEOn + "off" -> Right FEOff + r -> Left $ "bad GroupFeatureEnabled " <> B.unpack r + strP = strDecode <$?> A.takeByteString + +instance FromJSON GroupFeatureEnabled where + parseJSON = strParseJSON "GroupFeatureEnabled" + +instance ToJSON GroupFeatureEnabled where + toJSON = strToJSON + toEncoding = strToJEncoding + +groupFeatureState :: GroupFeatureI f => GroupFeaturePreference f -> (GroupFeatureEnabled, Maybe Int) +groupFeatureState p = + let enable = getField @"enable" p + param = if enable == FEOn then groupPrefParam p else Nothing + in (enable, param) + +mergePreferences :: Maybe Preferences -> Maybe Preferences -> FullPreferences +mergePreferences contactPrefs userPreferences = + FullPreferences + { timedMessages = pref SCFTimedMessages, + fullDelete = pref SCFFullDelete, + reactions = pref SCFReactions, + voice = pref SCFVoice, + calls = pref SCFCalls + } + where + pref :: SChatFeature f -> FeaturePreference f + pref f = + let sel = chatPrefSel f + in fromMaybe (getPreference f defaultChatPrefs) $ (contactPrefs >>= sel) <|> (userPreferences >>= sel) + +mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences +mergeGroupPreferences groupPreferences = + FullGroupPreferences + { timedMessages = pref SGFTimedMessages, + directMessages = pref SGFDirectMessages, + fullDelete = pref SGFFullDelete, + reactions = pref SGFReactions, + voice = pref SGFVoice, + files = pref SGFFiles + } + where + pref :: SGroupFeature f -> GroupFeaturePreference f + pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt) + +toGroupPreferences :: FullGroupPreferences -> GroupPreferences +toGroupPreferences groupPreferences = + GroupPreferences + { timedMessages = pref SGFTimedMessages, + directMessages = pref SGFDirectMessages, + fullDelete = pref SGFFullDelete, + reactions = pref SGFReactions, + voice = pref SGFVoice, + files = pref SGFFiles + } + where + pref :: SGroupFeature f -> Maybe (GroupFeaturePreference f) + pref f = Just $ getGroupPreference f groupPreferences + +data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON PrefEnabled where + toJSON = J.genericToJSON J.defaultOptions + toEncoding = J.genericToEncoding J.defaultOptions + +prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled +prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of + (FAAlways, FANo) -> PrefEnabled {forUser = False, forContact = asymmetric} + (FANo, FAAlways) -> PrefEnabled {forUser = asymmetric, forContact = False} + (_, FANo) -> PrefEnabled False False + (FANo, _) -> PrefEnabled False False + _ -> PrefEnabled True True + +prefStateText :: ChatFeature -> FeatureAllowed -> Maybe Int -> Text +prefStateText feature allowed param = case allowed of + FANo -> "cancelled " <> chatFeatureNameText feature + _ -> "offered " <> chatFeatureNameText feature <> paramText_ feature param + +featureStateText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text +featureStateText feature enabled param = + chatFeatureNameText feature <> ": " <> prefEnabledToText feature enabled param <> case enabled of + PrefEnabled {forUser = True} -> paramText_ feature param + _ -> "" + +paramText_ :: ChatFeature -> Maybe Int -> Text +paramText_ feature param = case feature of + CFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param + _ -> "" + +prefEnabledToText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text +prefEnabledToText f enabled param = case enabled of + PrefEnabled True True -> enabledStr + PrefEnabled False False -> "off" + PrefEnabled {forUser = True, forContact = False} -> enabledStr <> " for you" + PrefEnabled {forUser = False, forContact = True} -> enabledStr <> " for contact" + where + enabledStr = case f of + CFTimedMessages -> if isJust param then "enabled" else "allowed" + _ -> "enabled" + +preferenceText :: forall f. FeatureI f => FeaturePreference f -> Text +preferenceText p = + let feature = chatFeature $ sFeature @f + allowed = getField @"allow" p + paramText = if allowed == FAAlways || allowed == FAYes then paramText_ feature (prefParam p) else "" + in safeDecodeUtf8 (strEncode allowed) <> paramText + +featureState :: FeatureI f => ContactUserPreference (FeaturePreference f) -> (PrefEnabled, Maybe Int) +featureState ContactUserPreference {enabled, userPreference} = + let param = if forUser enabled then prefParam $ preference userPreference else Nothing + in (enabled, param) + +preferenceState :: FeatureI f => FeaturePreference f -> (FeatureAllowed, Maybe Int) +preferenceState pref = + let allow = getField @"allow" pref + param = if allow == FAAlways || allow == FAYes then prefParam pref else Nothing + in (allow, param) + +getContactUserPreference :: SChatFeature f -> ContactUserPreferences -> ContactUserPreference (FeaturePreference f) +getContactUserPreference = \case + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls diff --git a/src/Simplex/Chat/Types/Util.hs b/src/Simplex/Chat/Types/Util.hs new file mode 100644 index 0000000000..fffdd24b9e --- /dev/null +++ b/src/Simplex/Chat/Types/Util.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE LambdaCase #-} + +module Simplex.Chat.Types.Util where + +import Data.Aeson (ToJSON, FromJSON) +import qualified Data.Aeson as J +import Data.ByteString (ByteString) +import qualified Data.ByteString.Lazy.Char8 as LB +import Data.Text (Text) +import Data.Text.Encoding (encodeUtf8) +import Data.Typeable +import Database.SQLite.Simple (ResultError (..), SQLData (..)) +import Database.SQLite.Simple.FromField (FieldParser, returnError) +import Database.SQLite.Simple.Internal (Field (..)) +import Database.SQLite.Simple.Ok (Ok (Ok)) +import Simplex.Messaging.Util (safeDecodeUtf8) + +encodeJSON :: ToJSON a => a -> Text +encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode + +decodeJSON :: FromJSON a => Text -> Maybe a +decodeJSON = J.decode . LB.fromStrict . encodeUtf8 + +fromBlobField_ :: Typeable k => (ByteString -> Either String k) -> FieldParser k +fromBlobField_ p = \case + f@(Field (SQLBlob b) _) -> + case p b of + Right k -> Ok k + Left e -> returnError ConversionFailed f ("could not parse field: " ++ e) + f -> returnError ConversionFailed f "expecting SQLBlob column type" diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 6002665766..0b7db12f94 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -42,6 +42,7 @@ import Simplex.Chat.Protocol import Simplex.Chat.Store (AutoAccept (..), StoreError (..), UserContactLink (..)) import Simplex.Chat.Styled import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import qualified Simplex.FileTransfer.Protocol as XFTP import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..)) import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 9a3b3b09c5..c6acf986a1 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -20,6 +20,7 @@ import qualified Data.Text as T import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig) import Simplex.Chat.Store.Profiles (getUserContactProfiles) import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Store.SQLite (withTransaction) import Simplex.Messaging.Encoding.String import System.Directory (doesFileExist) diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index c0956f2876..6f7e0b8cf4 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -10,6 +10,7 @@ import Data.ByteString.Char8 (ByteString) import Data.Time.Clock.System (SystemTime (..), systemToUTCTime) import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet From 9c49b038cd063bf4af12f8bcff167bc9bcf59524 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 21 Jul 2023 21:32:28 +0100 Subject: [PATCH 11/18] core: split preferences to separate file --- simplex-chat.cabal | 2 + src/Simplex/Chat.hs | 2 + src/Simplex/Chat/Call.hs | 3 +- src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/Markdown.hs | 1 + src/Simplex/Chat/Messages.hs | 1 + src/Simplex/Chat/Messages/CIContent.hs | 2 + src/Simplex/Chat/Protocol.hs | 1 + src/Simplex/Chat/Store/Connections.hs | 1 + src/Simplex/Chat/Store/Direct.hs | 1 + src/Simplex/Chat/Store/Groups.hs | 1 + src/Simplex/Chat/Store/Profiles.hs | 1 + src/Simplex/Chat/Store/Shared.hs | 1 + src/Simplex/Chat/Types.hs | 775 +----------------------- src/Simplex/Chat/Types/Preferences.hs | 778 +++++++++++++++++++++++++ src/Simplex/Chat/Types/Util.hs | 30 + src/Simplex/Chat/View.hs | 1 + tests/ChatTests/Utils.hs | 1 + tests/ProtocolTests.hs | 1 + 19 files changed, 834 insertions(+), 770 deletions(-) create mode 100644 src/Simplex/Chat/Types/Preferences.hs create mode 100644 src/Simplex/Chat/Types/Util.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 526051edb6..ba68c8621e 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -125,6 +125,8 @@ library Simplex.Chat.Terminal.Notification Simplex.Chat.Terminal.Output Simplex.Chat.Types + Simplex.Chat.Types.Preferences + Simplex.Chat.Types.Util Simplex.Chat.Util Simplex.Chat.View other-modules: diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9f4988271d..df8120b9cb 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -67,6 +67,8 @@ import Simplex.Chat.Store.Messages import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences +import Simplex.Chat.Types.Util import Simplex.FileTransfer.Client.Main (maxFileSize) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.FileTransfer.Description (ValidFileDescription, gb, kb, mb) diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index 4483e701b8..7a738512bd 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -21,7 +21,8 @@ import Data.Time.Clock (UTCTime) import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) -import Simplex.Chat.Types (Contact, ContactId, User, decodeJSON, encodeJSON) +import Simplex.Chat.Types (Contact, ContactId, User) +import Simplex.Chat.Types.Util (decodeJSON, encodeJSON) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 467857a86a..8091320717 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -47,6 +47,7 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Protocol import Simplex.Chat.Store (AutoAccept, StoreError, UserContactLink, UserMsgReceiptSettings) import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent (AgentClient) import Simplex.Messaging.Agent.Client (AgentLocks, ProtocolTestFailure) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index b0beccb86b..1393831630 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -28,6 +28,7 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import GHC.Generics import Simplex.Chat.Types +import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, sumTypeJSON) diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 23baa618ab..f2d6553a5a 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,6 +35,7 @@ import Simplex.Chat.Markdown import Simplex.Chat.Messages.CIContent import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON) diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 40b139783b..725cf74cf9 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -29,6 +29,8 @@ import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences +import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 62f8055da6..31d1eb5738 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -41,6 +41,7 @@ import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) import Simplex.Chat.Call import Simplex.Chat.Types +import Simplex.Chat.Types.Util import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 416986173c..4b66291fe9 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -23,6 +23,7 @@ import Simplex.Chat.Store.Groups import Simplex.Chat.Store.Shared import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Store.SQLite (firstRow, firstRow') getConnectionEntity :: DB.Connection -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index da03ed3a7c..944527ae48 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -69,6 +69,7 @@ import qualified Database.SQLite.Simple as DB import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index c622f3e87b..c8b58fa857 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -97,6 +97,7 @@ import Simplex.Chat.Messages import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Crypto as C diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 797f237f79..608befb54d 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -74,6 +74,7 @@ import Simplex.Chat.Protocol import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Crypto as C diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index fc1444a8a9..6f368a774a 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -31,6 +31,7 @@ import GHC.Generics (Generic) import Simplex.Chat.Messages import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 1ddf3c3845..33a6bd0456 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -13,9 +13,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StrictData #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} @@ -24,7 +22,6 @@ module Simplex.Chat.Types where -import Control.Applicative ((<|>)) import Crypto.Number.Serialize (os2ip) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as J @@ -33,27 +30,22 @@ import qualified Data.Aeson.Types as JT import qualified Data.Attoparsec.ByteString.Char8 as A import Data.ByteString.Char8 (ByteString, pack, unpack) import qualified Data.ByteString.Char8 as B -import qualified Data.ByteString.Lazy.Char8 as LB import Data.Int (Int64) -import Data.Maybe (fromMaybe, isJust) +import Data.Maybe (isJust) import Data.Text (Text) import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) import Data.Time.Clock (UTCTime) -import Data.Typeable -import Database.SQLite.Simple (ResultError (..), SQLData (..)) -import Database.SQLite.Simple.FromField (FieldParser, FromField (..), returnError) -import Database.SQLite.Simple.Internal (Field (..)) -import Database.SQLite.Simple.Ok (Ok (Ok)) +import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) -import GHC.Records.Compat +import Simplex.Chat.Types.Preferences +import Simplex.Chat.Types.Util import Simplex.FileTransfer.Description (FileDigest) import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, sumTypeJSON, taggedObjectJSON) +import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI) -import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) +import Simplex.Messaging.Util ((<$?>)) class IsContact a where contactId' :: a -> ContactId @@ -353,659 +345,14 @@ defaultChatSettings = ChatSettings pattern DisableNtfs :: ChatSettings pattern DisableNtfs <- ChatSettings {enableNtfs = False} -data ChatFeature - = CFTimedMessages - | CFFullDelete - | CFReactions - | CFVoice - | CFCalls - deriving (Show, Generic) - -data SChatFeature (f :: ChatFeature) where - SCFTimedMessages :: SChatFeature 'CFTimedMessages - SCFFullDelete :: SChatFeature 'CFFullDelete - SCFReactions :: SChatFeature 'CFReactions - SCFVoice :: SChatFeature 'CFVoice - SCFCalls :: SChatFeature 'CFCalls - -deriving instance Show (SChatFeature f) - -data AChatFeature = forall f. FeatureI f => ACF (SChatFeature f) - -deriving instance Show AChatFeature - -chatFeatureNameText :: ChatFeature -> Text -chatFeatureNameText = \case - CFTimedMessages -> "Disappearing messages" - CFFullDelete -> "Full deletion" - CFReactions -> "Message reactions" - CFVoice -> "Voice messages" - CFCalls -> "Audio/video calls" - -chatFeatureNameText' :: SChatFeature f -> Text -chatFeatureNameText' = chatFeatureNameText . chatFeature - featureAllowed :: SChatFeature f -> (PrefEnabled -> Bool) -> Contact -> Bool featureAllowed feature forWhom Contact {mergedPreferences} = let ContactUserPreference {enabled} = getContactUserPreference feature mergedPreferences in forWhom enabled -instance ToJSON ChatFeature where - toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CF" - toJSON = J.genericToJSON . enumJSON $ dropPrefix "CF" - -instance FromJSON ChatFeature where - parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CF" - -allChatFeatures :: [AChatFeature] -allChatFeatures = - [ ACF SCFTimedMessages, - ACF SCFFullDelete, - ACF SCFReactions, - ACF SCFVoice, - ACF SCFCalls - ] - -chatPrefSel :: SChatFeature f -> Preferences -> Maybe (FeaturePreference f) -chatPrefSel = \case - SCFTimedMessages -> timedMessages - SCFFullDelete -> fullDelete - SCFReactions -> reactions - SCFVoice -> voice - SCFCalls -> calls - -chatFeature :: SChatFeature f -> ChatFeature -chatFeature = \case - SCFTimedMessages -> CFTimedMessages - SCFFullDelete -> CFFullDelete - SCFReactions -> CFReactions - SCFVoice -> CFVoice - SCFCalls -> CFCalls - -class PreferenceI p where - getPreference :: SChatFeature f -> p -> FeaturePreference f - -instance PreferenceI Preferences where - getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f prefs) - -instance PreferenceI (Maybe Preferences) where - getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f =<< prefs) - -instance PreferenceI FullPreferences where - getPreference = \case - SCFTimedMessages -> timedMessages - SCFFullDelete -> fullDelete - SCFReactions -> reactions - SCFVoice -> voice - SCFCalls -> calls - {-# INLINE getPreference #-} - -setPreference :: forall f. FeatureI f => SChatFeature f -> Maybe FeatureAllowed -> Maybe Preferences -> Preferences -setPreference f allow_ prefs_ = setPreference_ f pref $ fromMaybe emptyChatPrefs prefs_ - where - pref = setAllow <$> allow_ - setAllow :: FeatureAllowed -> FeaturePreference f - setAllow = setField @"allow" (getPreference f prefs) - prefs = mergePreferences Nothing prefs_ - -setPreference' :: SChatFeature f -> Maybe (FeaturePreference f) -> Maybe Preferences -> Preferences -setPreference' f pref_ prefs_ = setPreference_ f pref_ $ fromMaybe emptyChatPrefs prefs_ - -setPreference_ :: SChatFeature f -> Maybe (FeaturePreference f) -> Preferences -> Preferences -setPreference_ f pref_ prefs = - case f of - SCFTimedMessages -> prefs {timedMessages = pref_} - SCFFullDelete -> prefs {fullDelete = pref_} - SCFReactions -> prefs {reactions = pref_} - SCFVoice -> prefs {voice = pref_} - SCFCalls -> prefs {calls = pref_} - --- collection of optional chat preferences for the user and the contact -data Preferences = Preferences - { timedMessages :: Maybe TimedMessagesPreference, - fullDelete :: Maybe FullDeletePreference, - reactions :: Maybe ReactionsPreference, - voice :: Maybe VoicePreference, - calls :: Maybe CallsPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON Preferences where - toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} - toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} - -instance ToField Preferences where - toField = toField . encodeJSON - -instance FromField Preferences where - fromField = fromTextField_ decodeJSON - -data GroupFeature - = GFTimedMessages - | GFDirectMessages - | GFFullDelete - | GFReactions - | GFVoice - | GFFiles - deriving (Show, Generic) - -data SGroupFeature (f :: GroupFeature) where - SGFTimedMessages :: SGroupFeature 'GFTimedMessages - SGFDirectMessages :: SGroupFeature 'GFDirectMessages - SGFFullDelete :: SGroupFeature 'GFFullDelete - SGFReactions :: SGroupFeature 'GFReactions - SGFVoice :: SGroupFeature 'GFVoice - SGFFiles :: SGroupFeature 'GFFiles - -deriving instance Show (SGroupFeature f) - -data AGroupFeature = forall f. GroupFeatureI f => AGF (SGroupFeature f) - -deriving instance Show AGroupFeature - -groupFeatureNameText :: GroupFeature -> Text -groupFeatureNameText = \case - GFTimedMessages -> "Disappearing messages" - GFDirectMessages -> "Direct messages" - GFFullDelete -> "Full deletion" - GFReactions -> "Message reactions" - GFVoice -> "Voice messages" - GFFiles -> "Files and media" - -groupFeatureNameText' :: SGroupFeature f -> Text -groupFeatureNameText' = groupFeatureNameText . toGroupFeature - groupFeatureAllowed :: GroupFeatureI f => SGroupFeature f -> GroupInfo -> Bool groupFeatureAllowed feature gInfo = groupFeatureAllowed' feature $ fullGroupPreferences gInfo -groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferences -> Bool -groupFeatureAllowed' feature prefs = - getField @"enable" (getGroupPreference feature prefs) == FEOn - -instance ToJSON GroupFeature where - toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF" - toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF" - -instance FromJSON GroupFeature where - parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF" - -allGroupFeatures :: [AGroupFeature] -allGroupFeatures = - [ AGF SGFTimedMessages, - AGF SGFDirectMessages, - AGF SGFFullDelete, - AGF SGFReactions, - AGF SGFVoice, - AGF SGFFiles - ] - -groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) -groupPrefSel = \case - SGFTimedMessages -> timedMessages - SGFDirectMessages -> directMessages - SGFFullDelete -> fullDelete - SGFReactions -> reactions - SGFVoice -> voice - SGFFiles -> files - -toGroupFeature :: SGroupFeature f -> GroupFeature -toGroupFeature = \case - SGFTimedMessages -> GFTimedMessages - SGFDirectMessages -> GFDirectMessages - SGFFullDelete -> GFFullDelete - SGFReactions -> GFReactions - SGFVoice -> GFVoice - SGFFiles -> GFFiles - -class GroupPreferenceI p where - getGroupPreference :: SGroupFeature f -> p -> GroupFeaturePreference f - -instance GroupPreferenceI GroupPreferences where - getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs) - -instance GroupPreferenceI (Maybe GroupPreferences) where - getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) - -instance GroupPreferenceI FullGroupPreferences where - getGroupPreference = \case - SGFTimedMessages -> timedMessages - SGFDirectMessages -> directMessages - SGFFullDelete -> fullDelete - SGFReactions -> reactions - SGFVoice -> voice - SGFFiles -> files - {-# INLINE getGroupPreference #-} - --- collection of optional group preferences -data GroupPreferences = GroupPreferences - { timedMessages :: Maybe TimedMessagesGroupPreference, - directMessages :: Maybe DirectMessagesGroupPreference, - fullDelete :: Maybe FullDeleteGroupPreference, - reactions :: Maybe ReactionsGroupPreference, - voice :: Maybe VoiceGroupPreference, - files :: Maybe FilesGroupPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON GroupPreferences where - toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} - toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} - -instance ToField GroupPreferences where - toField = toField . encodeJSON - -instance FromField GroupPreferences where - fromField = fromTextField_ decodeJSON - -setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences -setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs - where - prefs = mergeGroupPreferences prefs_ - pref :: GroupFeaturePreference f - pref = setField @"enable" (getGroupPreference f prefs) enable - -setGroupPreference' :: SGroupFeature f -> GroupFeaturePreference f -> Maybe GroupPreferences -> GroupPreferences -setGroupPreference' f pref prefs_ = setGroupPreference_ f pref prefs - where - prefs = mergeGroupPreferences prefs_ - -setGroupPreference_ :: SGroupFeature f -> GroupFeaturePreference f -> FullGroupPreferences -> GroupPreferences -setGroupPreference_ f pref prefs = - toGroupPreferences $ case f of - SGFTimedMessages -> prefs {timedMessages = pref} - SGFDirectMessages -> prefs {directMessages = pref} - SGFFullDelete -> prefs {fullDelete = pref} - SGFReactions -> prefs {reactions = pref} - SGFVoice -> prefs {voice = pref} - SGFFiles -> prefs {files = pref} - -setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences -setGroupTimedMessagesPreference pref prefs_ = - toGroupPreferences $ prefs {timedMessages = pref} - where - prefs = mergeGroupPreferences prefs_ - --- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing --- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here. -data FullPreferences = FullPreferences - { timedMessages :: TimedMessagesPreference, - fullDelete :: FullDeletePreference, - reactions :: ReactionsPreference, - voice :: VoicePreference, - calls :: CallsPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.defaultOptions - --- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing --- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here. -data FullGroupPreferences = FullGroupPreferences - { timedMessages :: TimedMessagesGroupPreference, - directMessages :: DirectMessagesGroupPreference, - fullDelete :: FullDeleteGroupPreference, - reactions :: ReactionsGroupPreference, - voice :: VoiceGroupPreference, - files :: FilesGroupPreference - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON FullGroupPreferences where toEncoding = J.genericToEncoding J.defaultOptions - --- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences -data ContactUserPreferences = ContactUserPreferences - { timedMessages :: ContactUserPreference TimedMessagesPreference, - fullDelete :: ContactUserPreference FullDeletePreference, - reactions :: ContactUserPreference ReactionsPreference, - voice :: ContactUserPreference VoicePreference, - calls :: ContactUserPreference CallsPreference - } - deriving (Eq, Show, Generic) - -data ContactUserPreference p = ContactUserPreference - { enabled :: PrefEnabled, - userPreference :: ContactUserPref p, - contactPreference :: p - } - deriving (Eq, Show, Generic) - -data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p} - deriving (Eq, Show, Generic) - -instance ToJSON ContactUserPreferences where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON p => ToJSON (ContactUserPreference p) where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON p => ToJSON (ContactUserPref p) where - toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CUP" - toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CUP" - -toChatPrefs :: FullPreferences -> Preferences -toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} = - Preferences - { timedMessages = Just timedMessages, - fullDelete = Just fullDelete, - reactions = Just reactions, - voice = Just voice, - calls = Just calls - } - -defaultChatPrefs :: FullPreferences -defaultChatPrefs = - FullPreferences - { timedMessages = TimedMessagesPreference {allow = FAYes, ttl = Nothing}, - fullDelete = FullDeletePreference {allow = FANo}, - reactions = ReactionsPreference {allow = FAYes}, - voice = VoicePreference {allow = FAYes}, - calls = CallsPreference {allow = FAYes} - } - -emptyChatPrefs :: Preferences -emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing - -defaultGroupPrefs :: FullGroupPreferences -defaultGroupPrefs = - FullGroupPreferences - { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400}, - directMessages = DirectMessagesGroupPreference {enable = FEOff}, - fullDelete = FullDeleteGroupPreference {enable = FEOff}, - reactions = ReactionsGroupPreference {enable = FEOn}, - voice = VoiceGroupPreference {enable = FEOn}, - files = FilesGroupPreference {enable = FEOn} - } - -emptyGroupPrefs :: GroupPreferences -emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing - -data TimedMessagesPreference = TimedMessagesPreference - { allow :: FeatureAllowed, - ttl :: Maybe Int - } - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON TimedMessagesPreference where - toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} - toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} - -data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON FullDeletePreference where toEncoding = J.genericToEncoding J.defaultOptions - -data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON ReactionsPreference where toEncoding = J.genericToEncoding J.defaultOptions - -data VoicePreference = VoicePreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON VoicePreference where toEncoding = J.genericToEncoding J.defaultOptions - -data CallsPreference = CallsPreference {allow :: FeatureAllowed} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON CallsPreference where toEncoding = J.genericToEncoding J.defaultOptions - -class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where - type FeaturePreference (f :: ChatFeature) = p | p -> f - sFeature :: SChatFeature f - prefParam :: FeaturePreference f -> Maybe Int - -instance HasField "allow" TimedMessagesPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: TimedMessagesPreference)) - -instance HasField "allow" FullDeletePreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: FullDeletePreference)) - -instance HasField "allow" ReactionsPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: ReactionsPreference)) - -instance HasField "allow" VoicePreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: VoicePreference)) - -instance HasField "allow" CallsPreference FeatureAllowed where - hasField p = (\allow -> p {allow}, allow (p :: CallsPreference)) - -instance FeatureI 'CFTimedMessages where - type FeaturePreference 'CFTimedMessages = TimedMessagesPreference - sFeature = SCFTimedMessages - prefParam TimedMessagesPreference {ttl} = ttl - -instance FeatureI 'CFFullDelete where - type FeaturePreference 'CFFullDelete = FullDeletePreference - sFeature = SCFFullDelete - prefParam _ = Nothing - -instance FeatureI 'CFReactions where - type FeaturePreference 'CFReactions = ReactionsPreference - sFeature = SCFReactions - prefParam _ = Nothing - -instance FeatureI 'CFVoice where - type FeaturePreference 'CFVoice = VoicePreference - sFeature = SCFVoice - prefParam _ = Nothing - -instance FeatureI 'CFCalls where - type FeaturePreference 'CFCalls = CallsPreference - sFeature = SCFCalls - prefParam _ = Nothing - -data GroupPreference = GroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data TimedMessagesGroupPreference = TimedMessagesGroupPreference - { enable :: GroupFeatureEnabled, - ttl :: Maybe Int - } - deriving (Eq, Show, Generic, FromJSON) - -data DirectMessagesGroupPreference = DirectMessagesGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data FullDeleteGroupPreference = FullDeleteGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data ReactionsGroupPreference = ReactionsGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data VoiceGroupPreference = VoiceGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -data FilesGroupPreference = FilesGroupPreference - {enable :: GroupFeatureEnabled} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON GroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON TimedMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON DirectMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON ReactionsGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON FullDeleteGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON VoiceGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -instance ToJSON FilesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions - -class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where - type GroupFeaturePreference (f :: GroupFeature) = p | p -> f - sGroupFeature :: SGroupFeature f - groupPrefParam :: GroupFeaturePreference f -> Maybe Int - -instance HasField "enable" GroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: GroupPreference)) - -instance HasField "enable" TimedMessagesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: TimedMessagesGroupPreference)) - -instance HasField "enable" DirectMessagesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: DirectMessagesGroupPreference)) - -instance HasField "enable" ReactionsGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: ReactionsGroupPreference)) - -instance HasField "enable" FullDeleteGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: FullDeleteGroupPreference)) - -instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: VoiceGroupPreference)) - -instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where - hasField p = (\enable -> p {enable}, enable (p :: FilesGroupPreference)) - -instance GroupFeatureI 'GFTimedMessages where - type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference - sGroupFeature = SGFTimedMessages - groupPrefParam TimedMessagesGroupPreference {ttl} = ttl - -instance GroupFeatureI 'GFDirectMessages where - type GroupFeaturePreference 'GFDirectMessages = DirectMessagesGroupPreference - sGroupFeature = SGFDirectMessages - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFFullDelete where - type GroupFeaturePreference 'GFFullDelete = FullDeleteGroupPreference - sGroupFeature = SGFFullDelete - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFReactions where - type GroupFeaturePreference 'GFReactions = ReactionsGroupPreference - sGroupFeature = SGFReactions - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFVoice where - type GroupFeaturePreference 'GFVoice = VoiceGroupPreference - sGroupFeature = SGFVoice - groupPrefParam _ = Nothing - -instance GroupFeatureI 'GFFiles where - type GroupFeaturePreference 'GFFiles = FilesGroupPreference - sGroupFeature = SGFFiles - groupPrefParam _ = Nothing - -groupPrefStateText :: HasField "enable" p GroupFeatureEnabled => GroupFeature -> p -> Maybe Int -> Text -groupPrefStateText feature pref param = - let enabled = getField @"enable" pref - paramText = if enabled == FEOn then groupParamText_ feature param else "" - in groupFeatureNameText feature <> ": " <> safeDecodeUtf8 (strEncode enabled) <> paramText - -groupParamText_ :: GroupFeature -> Maybe Int -> Text -groupParamText_ feature param = case feature of - GFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param - _ -> "" - -groupPreferenceText :: forall f. GroupFeatureI f => GroupFeaturePreference f -> Text -groupPreferenceText pref = - let feature = toGroupFeature $ sGroupFeature @f - in groupPrefStateText feature pref $ groupPrefParam pref - -timedTTLText :: Int -> Text -timedTTLText 0 = "0 sec" -timedTTLText ttl = do - let (m', s) = ttl `quotRem` 60 - (h', m) = m' `quotRem` 60 - (d', h) = h' `quotRem` 24 - (mm, d) = d' `quotRem` 30 - T.pack . unwords $ - [mms mm | mm /= 0] <> [ds d | d /= 0] <> [hs h | h /= 0] <> [ms m | m /= 0] <> [ss s | s /= 0] - where - ss s = show s <> " sec" - ms m = show m <> " min" - hs 1 = "1 hour" - hs h = show h <> " hours" - ds 1 = "1 day" - ds 7 = "1 week" - ds 14 = "2 weeks" - ds d = show d <> " days" - mms 1 = "1 month" - mms mm = show mm <> " months" - -toGroupPreference :: GroupFeatureI f => GroupFeaturePreference f -> GroupPreference -toGroupPreference p = GroupPreference {enable = getField @"enable" p} - -data FeatureAllowed - = FAAlways -- allow unconditionally - | FAYes -- allow, if peer allows it - | FANo -- do not allow - deriving (Eq, Show, Generic) - -instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode - -instance ToField FeatureAllowed where toField = toField . strEncode - -instance StrEncoding FeatureAllowed where - strEncode = \case - FAAlways -> "always" - FAYes -> "yes" - FANo -> "no" - strDecode = \case - "always" -> Right FAAlways - "yes" -> Right FAYes - "no" -> Right FANo - r -> Left $ "bad FeatureAllowed " <> B.unpack r - strP = strDecode <$?> A.takeByteString - -instance FromJSON FeatureAllowed where - parseJSON = strParseJSON "FeatureAllowed" - -instance ToJSON FeatureAllowed where - toJSON = strToJSON - toEncoding = strToJEncoding - -data GroupFeatureEnabled = FEOn | FEOff - deriving (Eq, Show, Generic) - -instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode - -instance ToField GroupFeatureEnabled where toField = toField . strEncode - -instance StrEncoding GroupFeatureEnabled where - strEncode = \case - FEOn -> "on" - FEOff -> "off" - strDecode = \case - "on" -> Right FEOn - "off" -> Right FEOff - r -> Left $ "bad GroupFeatureEnabled " <> B.unpack r - strP = strDecode <$?> A.takeByteString - -instance FromJSON GroupFeatureEnabled where - parseJSON = strParseJSON "GroupFeatureEnabled" - -instance ToJSON GroupFeatureEnabled where - toJSON = strToJSON - toEncoding = strToJEncoding - -groupFeatureState :: GroupFeatureI f => GroupFeaturePreference f -> (GroupFeatureEnabled, Maybe Int) -groupFeatureState p = - let enable = getField @"enable" p - param = if enable == FEOn then groupPrefParam p else Nothing - in (enable, param) - -mergePreferences :: Maybe Preferences -> Maybe Preferences -> FullPreferences -mergePreferences contactPrefs userPreferences = - FullPreferences - { timedMessages = pref SCFTimedMessages, - fullDelete = pref SCFFullDelete, - reactions = pref SCFReactions, - voice = pref SCFVoice, - calls = pref SCFCalls - } - where - pref :: SChatFeature f -> FeaturePreference f - pref f = - let sel = chatPrefSel f - in fromMaybe (getPreference f defaultChatPrefs) $ (contactPrefs >>= sel) <|> (userPreferences >>= sel) - mergeUserChatPrefs :: User -> Contact -> FullPreferences mergeUserChatPrefs user ct = mergeUserChatPrefs' user (contactConnIncognito ct) (userPreferences ct) @@ -1014,94 +361,6 @@ mergeUserChatPrefs' user connectedIncognito userPreferences = let userPrefs = if connectedIncognito then Nothing else preferences' user in mergePreferences (Just userPreferences) userPrefs -mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences -mergeGroupPreferences groupPreferences = - FullGroupPreferences - { timedMessages = pref SGFTimedMessages, - directMessages = pref SGFDirectMessages, - fullDelete = pref SGFFullDelete, - reactions = pref SGFReactions, - voice = pref SGFVoice, - files = pref SGFFiles - } - where - pref :: SGroupFeature f -> GroupFeaturePreference f - pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt) - -toGroupPreferences :: FullGroupPreferences -> GroupPreferences -toGroupPreferences groupPreferences = - GroupPreferences - { timedMessages = pref SGFTimedMessages, - directMessages = pref SGFDirectMessages, - fullDelete = pref SGFFullDelete, - reactions = pref SGFReactions, - voice = pref SGFVoice, - files = pref SGFFiles - } - where - pref :: SGroupFeature f -> Maybe (GroupFeaturePreference f) - pref f = Just $ getGroupPreference f groupPreferences - -data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool} - deriving (Eq, Show, Generic, FromJSON) - -instance ToJSON PrefEnabled where - toJSON = J.genericToJSON J.defaultOptions - toEncoding = J.genericToEncoding J.defaultOptions - -prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled -prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of - (FAAlways, FANo) -> PrefEnabled {forUser = False, forContact = asymmetric} - (FANo, FAAlways) -> PrefEnabled {forUser = asymmetric, forContact = False} - (_, FANo) -> PrefEnabled False False - (FANo, _) -> PrefEnabled False False - _ -> PrefEnabled True True - -prefStateText :: ChatFeature -> FeatureAllowed -> Maybe Int -> Text -prefStateText feature allowed param = case allowed of - FANo -> "cancelled " <> chatFeatureNameText feature - _ -> "offered " <> chatFeatureNameText feature <> paramText_ feature param - -featureStateText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text -featureStateText feature enabled param = - chatFeatureNameText feature <> ": " <> prefEnabledToText feature enabled param <> case enabled of - PrefEnabled {forUser = True} -> paramText_ feature param - _ -> "" - -paramText_ :: ChatFeature -> Maybe Int -> Text -paramText_ feature param = case feature of - CFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param - _ -> "" - -prefEnabledToText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text -prefEnabledToText f enabled param = case enabled of - PrefEnabled True True -> enabledStr - PrefEnabled False False -> "off" - PrefEnabled {forUser = True, forContact = False} -> enabledStr <> " for you" - PrefEnabled {forUser = False, forContact = True} -> enabledStr <> " for contact" - where - enabledStr = case f of - CFTimedMessages -> if isJust param then "enabled" else "allowed" - _ -> "enabled" - -preferenceText :: forall f. FeatureI f => FeaturePreference f -> Text -preferenceText p = - let feature = chatFeature $ sFeature @f - allowed = getField @"allow" p - paramText = if allowed == FAAlways || allowed == FAYes then paramText_ feature (prefParam p) else "" - in safeDecodeUtf8 (strEncode allowed) <> paramText - -featureState :: FeatureI f => ContactUserPreference (FeaturePreference f) -> (PrefEnabled, Maybe Int) -featureState ContactUserPreference {enabled, userPreference} = - let param = if forUser enabled then prefParam $ preference userPreference else Nothing - in (enabled, param) - -preferenceState :: FeatureI f => FeaturePreference f -> (FeatureAllowed, Maybe Int) -preferenceState pref = - let allow = getField @"allow" pref - param = if allow == FAAlways || allow == FAYes then prefParam pref else Nothing - in (allow, param) - updateMergedPreferences :: User -> Contact -> Contact updateMergedPreferences user ct = let mergedPreferences = contactUserPreferences user (userPreferences ct) (preferences' ct) (contactConnIncognito ct) @@ -1135,14 +394,6 @@ contactUserPreferences user userPreferences contactPreferences connectedIncognit ctUserPrefs = mergeUserChatPrefs' user connectedIncognito userPreferences ctPrefs = mergePreferences contactPreferences Nothing -getContactUserPreference :: SChatFeature f -> ContactUserPreferences -> ContactUserPreference (FeaturePreference f) -getContactUserPreference = \case - SCFTimedMessages -> timedMessages - SCFFullDelete -> fullDelete - SCFReactions -> reactions - SCFVoice -> voice - SCFCalls -> calls - data Profile = Profile { displayName :: ContactName, fullName :: Text, @@ -1433,14 +684,6 @@ instance ToJSON GroupMemberRole where toJSON = strToJSON toEncoding = strToJEncoding -fromBlobField_ :: Typeable k => (ByteString -> Either String k) -> FieldParser k -fromBlobField_ p = \case - f@(Field (SQLBlob b) _) -> - case p b of - Right k -> Ok k - Left e -> returnError ConversionFailed f ("could not parse field: " ++ e) - f -> returnError ConversionFailed f "expecting SQLBlob column type" - newtype Probe = Probe {unProbe :: ByteString} deriving (Eq, Show) @@ -2194,12 +1437,6 @@ data XGrpMemIntroCont = XGrpMemIntroCont } deriving (Show) -encodeJSON :: ToJSON a => a -> Text -encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode - -decodeJSON :: FromJSON a => Text -> Maybe a -decodeJSON = J.decode . LB.fromStrict . encodeUtf8 - data ServerCfg p = ServerCfg { server :: ProtoServerWithAuth p, preset :: Bool, diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs new file mode 100644 index 0000000000..a89e383242 --- /dev/null +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -0,0 +1,778 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilyDependencies #-} +{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} + +{-# HLINT ignore "Use newtype instead of data" #-} + +module Simplex.Chat.Types.Preferences where + +import Control.Applicative ((<|>)) +import Data.Aeson (FromJSON (..), ToJSON (..)) +import qualified Data.Aeson as J +import qualified Data.Attoparsec.ByteString.Char8 as A +import qualified Data.ByteString.Char8 as B +import Data.Maybe (fromMaybe, isJust) +import Data.Text (Text) +import qualified Data.Text as T +import Database.SQLite.Simple.FromField (FromField (..)) +import Database.SQLite.Simple.ToField (ToField (..)) +import GHC.Generics (Generic) +import GHC.Records.Compat +import Simplex.Chat.Types.Util +import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) + +data ChatFeature + = CFTimedMessages + | CFFullDelete + | CFReactions + | CFVoice + | CFCalls + deriving (Show, Generic) + +data SChatFeature (f :: ChatFeature) where + SCFTimedMessages :: SChatFeature 'CFTimedMessages + SCFFullDelete :: SChatFeature 'CFFullDelete + SCFReactions :: SChatFeature 'CFReactions + SCFVoice :: SChatFeature 'CFVoice + SCFCalls :: SChatFeature 'CFCalls + +deriving instance Show (SChatFeature f) + +data AChatFeature = forall f. FeatureI f => ACF (SChatFeature f) + +deriving instance Show AChatFeature + +chatFeatureNameText :: ChatFeature -> Text +chatFeatureNameText = \case + CFTimedMessages -> "Disappearing messages" + CFFullDelete -> "Full deletion" + CFReactions -> "Message reactions" + CFVoice -> "Voice messages" + CFCalls -> "Audio/video calls" + +chatFeatureNameText' :: SChatFeature f -> Text +chatFeatureNameText' = chatFeatureNameText . chatFeature + +instance ToJSON ChatFeature where + toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CF" + toJSON = J.genericToJSON . enumJSON $ dropPrefix "CF" + +instance FromJSON ChatFeature where + parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CF" + +allChatFeatures :: [AChatFeature] +allChatFeatures = + [ ACF SCFTimedMessages, + ACF SCFFullDelete, + ACF SCFReactions, + ACF SCFVoice, + ACF SCFCalls + ] + +chatPrefSel :: SChatFeature f -> Preferences -> Maybe (FeaturePreference f) +chatPrefSel = \case + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls + +chatFeature :: SChatFeature f -> ChatFeature +chatFeature = \case + SCFTimedMessages -> CFTimedMessages + SCFFullDelete -> CFFullDelete + SCFReactions -> CFReactions + SCFVoice -> CFVoice + SCFCalls -> CFCalls + +class PreferenceI p where + getPreference :: SChatFeature f -> p -> FeaturePreference f + +instance PreferenceI Preferences where + getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f prefs) + +instance PreferenceI (Maybe Preferences) where + getPreference f prefs = fromMaybe (getPreference f defaultChatPrefs) (chatPrefSel f =<< prefs) + +instance PreferenceI FullPreferences where + getPreference = \case + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls + {-# INLINE getPreference #-} + +setPreference :: forall f. FeatureI f => SChatFeature f -> Maybe FeatureAllowed -> Maybe Preferences -> Preferences +setPreference f allow_ prefs_ = setPreference_ f pref $ fromMaybe emptyChatPrefs prefs_ + where + pref = setAllow <$> allow_ + setAllow :: FeatureAllowed -> FeaturePreference f + setAllow = setField @"allow" (getPreference f prefs) + prefs = mergePreferences Nothing prefs_ + +setPreference' :: SChatFeature f -> Maybe (FeaturePreference f) -> Maybe Preferences -> Preferences +setPreference' f pref_ prefs_ = setPreference_ f pref_ $ fromMaybe emptyChatPrefs prefs_ + +setPreference_ :: SChatFeature f -> Maybe (FeaturePreference f) -> Preferences -> Preferences +setPreference_ f pref_ prefs = + case f of + SCFTimedMessages -> prefs {timedMessages = pref_} + SCFFullDelete -> prefs {fullDelete = pref_} + SCFReactions -> prefs {reactions = pref_} + SCFVoice -> prefs {voice = pref_} + SCFCalls -> prefs {calls = pref_} + +-- collection of optional chat preferences for the user and the contact +data Preferences = Preferences + { timedMessages :: Maybe TimedMessagesPreference, + fullDelete :: Maybe FullDeletePreference, + reactions :: Maybe ReactionsPreference, + voice :: Maybe VoicePreference, + calls :: Maybe CallsPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON Preferences where + toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} + toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} + +instance ToField Preferences where + toField = toField . encodeJSON + +instance FromField Preferences where + fromField = fromTextField_ decodeJSON + +data GroupFeature + = GFTimedMessages + | GFDirectMessages + | GFFullDelete + | GFReactions + | GFVoice + | GFFiles + deriving (Show, Generic) + +data SGroupFeature (f :: GroupFeature) where + SGFTimedMessages :: SGroupFeature 'GFTimedMessages + SGFDirectMessages :: SGroupFeature 'GFDirectMessages + SGFFullDelete :: SGroupFeature 'GFFullDelete + SGFReactions :: SGroupFeature 'GFReactions + SGFVoice :: SGroupFeature 'GFVoice + SGFFiles :: SGroupFeature 'GFFiles + +deriving instance Show (SGroupFeature f) + +data AGroupFeature = forall f. GroupFeatureI f => AGF (SGroupFeature f) + +deriving instance Show AGroupFeature + +groupFeatureNameText :: GroupFeature -> Text +groupFeatureNameText = \case + GFTimedMessages -> "Disappearing messages" + GFDirectMessages -> "Direct messages" + GFFullDelete -> "Full deletion" + GFReactions -> "Message reactions" + GFVoice -> "Voice messages" + GFFiles -> "Files and media" + +groupFeatureNameText' :: SGroupFeature f -> Text +groupFeatureNameText' = groupFeatureNameText . toGroupFeature + +groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferences -> Bool +groupFeatureAllowed' feature prefs = + getField @"enable" (getGroupPreference feature prefs) == FEOn + +instance ToJSON GroupFeature where + toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF" + toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF" + +instance FromJSON GroupFeature where + parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF" + +allGroupFeatures :: [AGroupFeature] +allGroupFeatures = + [ AGF SGFTimedMessages, + AGF SGFDirectMessages, + AGF SGFFullDelete, + AGF SGFReactions, + AGF SGFVoice, + AGF SGFFiles + ] + +groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) +groupPrefSel = \case + SGFTimedMessages -> timedMessages + SGFDirectMessages -> directMessages + SGFFullDelete -> fullDelete + SGFReactions -> reactions + SGFVoice -> voice + SGFFiles -> files + +toGroupFeature :: SGroupFeature f -> GroupFeature +toGroupFeature = \case + SGFTimedMessages -> GFTimedMessages + SGFDirectMessages -> GFDirectMessages + SGFFullDelete -> GFFullDelete + SGFReactions -> GFReactions + SGFVoice -> GFVoice + SGFFiles -> GFFiles + +class GroupPreferenceI p where + getGroupPreference :: SGroupFeature f -> p -> GroupFeaturePreference f + +instance GroupPreferenceI GroupPreferences where + getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs) + +instance GroupPreferenceI (Maybe GroupPreferences) where + getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) + +instance GroupPreferenceI FullGroupPreferences where + getGroupPreference = \case + SGFTimedMessages -> timedMessages + SGFDirectMessages -> directMessages + SGFFullDelete -> fullDelete + SGFReactions -> reactions + SGFVoice -> voice + SGFFiles -> files + {-# INLINE getGroupPreference #-} + +-- collection of optional group preferences +data GroupPreferences = GroupPreferences + { timedMessages :: Maybe TimedMessagesGroupPreference, + directMessages :: Maybe DirectMessagesGroupPreference, + fullDelete :: Maybe FullDeleteGroupPreference, + reactions :: Maybe ReactionsGroupPreference, + voice :: Maybe VoiceGroupPreference, + files :: Maybe FilesGroupPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON GroupPreferences where + toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} + toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} + +instance ToField GroupPreferences where + toField = toField . encodeJSON + +instance FromField GroupPreferences where + fromField = fromTextField_ decodeJSON + +setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences +setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs + where + prefs = mergeGroupPreferences prefs_ + pref :: GroupFeaturePreference f + pref = setField @"enable" (getGroupPreference f prefs) enable + +setGroupPreference' :: SGroupFeature f -> GroupFeaturePreference f -> Maybe GroupPreferences -> GroupPreferences +setGroupPreference' f pref prefs_ = setGroupPreference_ f pref prefs + where + prefs = mergeGroupPreferences prefs_ + +setGroupPreference_ :: SGroupFeature f -> GroupFeaturePreference f -> FullGroupPreferences -> GroupPreferences +setGroupPreference_ f pref prefs = + toGroupPreferences $ case f of + SGFTimedMessages -> prefs {timedMessages = pref} + SGFDirectMessages -> prefs {directMessages = pref} + SGFFullDelete -> prefs {fullDelete = pref} + SGFReactions -> prefs {reactions = pref} + SGFVoice -> prefs {voice = pref} + SGFFiles -> prefs {files = pref} + +setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences +setGroupTimedMessagesPreference pref prefs_ = + toGroupPreferences $ prefs {timedMessages = pref} + where + prefs = mergeGroupPreferences prefs_ + +-- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing +-- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here. +data FullPreferences = FullPreferences + { timedMessages :: TimedMessagesPreference, + fullDelete :: FullDeletePreference, + reactions :: ReactionsPreference, + voice :: VoicePreference, + calls :: CallsPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.defaultOptions + +-- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing +-- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here. +data FullGroupPreferences = FullGroupPreferences + { timedMessages :: TimedMessagesGroupPreference, + directMessages :: DirectMessagesGroupPreference, + fullDelete :: FullDeleteGroupPreference, + reactions :: ReactionsGroupPreference, + voice :: VoiceGroupPreference, + files :: FilesGroupPreference + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON FullGroupPreferences where toEncoding = J.genericToEncoding J.defaultOptions + +-- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences +data ContactUserPreferences = ContactUserPreferences + { timedMessages :: ContactUserPreference TimedMessagesPreference, + fullDelete :: ContactUserPreference FullDeletePreference, + reactions :: ContactUserPreference ReactionsPreference, + voice :: ContactUserPreference VoicePreference, + calls :: ContactUserPreference CallsPreference + } + deriving (Eq, Show, Generic) + +data ContactUserPreference p = ContactUserPreference + { enabled :: PrefEnabled, + userPreference :: ContactUserPref p, + contactPreference :: p + } + deriving (Eq, Show, Generic) + +data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p} + deriving (Eq, Show, Generic) + +instance ToJSON ContactUserPreferences where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON p => ToJSON (ContactUserPreference p) where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON p => ToJSON (ContactUserPref p) where + toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CUP" + toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CUP" + +toChatPrefs :: FullPreferences -> Preferences +toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} = + Preferences + { timedMessages = Just timedMessages, + fullDelete = Just fullDelete, + reactions = Just reactions, + voice = Just voice, + calls = Just calls + } + +defaultChatPrefs :: FullPreferences +defaultChatPrefs = + FullPreferences + { timedMessages = TimedMessagesPreference {allow = FAYes, ttl = Nothing}, + fullDelete = FullDeletePreference {allow = FANo}, + reactions = ReactionsPreference {allow = FAYes}, + voice = VoicePreference {allow = FAYes}, + calls = CallsPreference {allow = FAYes} + } + +emptyChatPrefs :: Preferences +emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing + +defaultGroupPrefs :: FullGroupPreferences +defaultGroupPrefs = + FullGroupPreferences + { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400}, + directMessages = DirectMessagesGroupPreference {enable = FEOff}, + fullDelete = FullDeleteGroupPreference {enable = FEOff}, + reactions = ReactionsGroupPreference {enable = FEOn}, + voice = VoiceGroupPreference {enable = FEOn}, + files = FilesGroupPreference {enable = FEOn} + } + +emptyGroupPrefs :: GroupPreferences +emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing + +data TimedMessagesPreference = TimedMessagesPreference + { allow :: FeatureAllowed, + ttl :: Maybe Int + } + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON TimedMessagesPreference where + toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} + toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} + +data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON FullDeletePreference where toEncoding = J.genericToEncoding J.defaultOptions + +data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON ReactionsPreference where toEncoding = J.genericToEncoding J.defaultOptions + +data VoicePreference = VoicePreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON VoicePreference where toEncoding = J.genericToEncoding J.defaultOptions + +data CallsPreference = CallsPreference {allow :: FeatureAllowed} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON CallsPreference where toEncoding = J.genericToEncoding J.defaultOptions + +class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where + type FeaturePreference (f :: ChatFeature) = p | p -> f + sFeature :: SChatFeature f + prefParam :: FeaturePreference f -> Maybe Int + +instance HasField "allow" TimedMessagesPreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: TimedMessagesPreference)) + +instance HasField "allow" FullDeletePreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: FullDeletePreference)) + +instance HasField "allow" ReactionsPreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: ReactionsPreference)) + +instance HasField "allow" VoicePreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: VoicePreference)) + +instance HasField "allow" CallsPreference FeatureAllowed where + hasField p = (\allow -> p {allow}, allow (p :: CallsPreference)) + +instance FeatureI 'CFTimedMessages where + type FeaturePreference 'CFTimedMessages = TimedMessagesPreference + sFeature = SCFTimedMessages + prefParam TimedMessagesPreference {ttl} = ttl + +instance FeatureI 'CFFullDelete where + type FeaturePreference 'CFFullDelete = FullDeletePreference + sFeature = SCFFullDelete + prefParam _ = Nothing + +instance FeatureI 'CFReactions where + type FeaturePreference 'CFReactions = ReactionsPreference + sFeature = SCFReactions + prefParam _ = Nothing + +instance FeatureI 'CFVoice where + type FeaturePreference 'CFVoice = VoicePreference + sFeature = SCFVoice + prefParam _ = Nothing + +instance FeatureI 'CFCalls where + type FeaturePreference 'CFCalls = CallsPreference + sFeature = SCFCalls + prefParam _ = Nothing + +data GroupPreference = GroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data TimedMessagesGroupPreference = TimedMessagesGroupPreference + { enable :: GroupFeatureEnabled, + ttl :: Maybe Int + } + deriving (Eq, Show, Generic, FromJSON) + +data DirectMessagesGroupPreference = DirectMessagesGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data FullDeleteGroupPreference = FullDeleteGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data ReactionsGroupPreference = ReactionsGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data VoiceGroupPreference = VoiceGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +data FilesGroupPreference = FilesGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON GroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON TimedMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON DirectMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON ReactionsGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON FullDeleteGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON VoiceGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +instance ToJSON FilesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions + +class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where + type GroupFeaturePreference (f :: GroupFeature) = p | p -> f + sGroupFeature :: SGroupFeature f + groupPrefParam :: GroupFeaturePreference f -> Maybe Int + +instance HasField "enable" GroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: GroupPreference)) + +instance HasField "enable" TimedMessagesGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: TimedMessagesGroupPreference)) + +instance HasField "enable" DirectMessagesGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: DirectMessagesGroupPreference)) + +instance HasField "enable" ReactionsGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: ReactionsGroupPreference)) + +instance HasField "enable" FullDeleteGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: FullDeleteGroupPreference)) + +instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: VoiceGroupPreference)) + +instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where + hasField p = (\enable -> p {enable}, enable (p :: FilesGroupPreference)) + +instance GroupFeatureI 'GFTimedMessages where + type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference + sGroupFeature = SGFTimedMessages + groupPrefParam TimedMessagesGroupPreference {ttl} = ttl + +instance GroupFeatureI 'GFDirectMessages where + type GroupFeaturePreference 'GFDirectMessages = DirectMessagesGroupPreference + sGroupFeature = SGFDirectMessages + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFFullDelete where + type GroupFeaturePreference 'GFFullDelete = FullDeleteGroupPreference + sGroupFeature = SGFFullDelete + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFReactions where + type GroupFeaturePreference 'GFReactions = ReactionsGroupPreference + sGroupFeature = SGFReactions + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFVoice where + type GroupFeaturePreference 'GFVoice = VoiceGroupPreference + sGroupFeature = SGFVoice + groupPrefParam _ = Nothing + +instance GroupFeatureI 'GFFiles where + type GroupFeaturePreference 'GFFiles = FilesGroupPreference + sGroupFeature = SGFFiles + groupPrefParam _ = Nothing + +groupPrefStateText :: HasField "enable" p GroupFeatureEnabled => GroupFeature -> p -> Maybe Int -> Text +groupPrefStateText feature pref param = + let enabled = getField @"enable" pref + paramText = if enabled == FEOn then groupParamText_ feature param else "" + in groupFeatureNameText feature <> ": " <> safeDecodeUtf8 (strEncode enabled) <> paramText + +groupParamText_ :: GroupFeature -> Maybe Int -> Text +groupParamText_ feature param = case feature of + GFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param + _ -> "" + +groupPreferenceText :: forall f. GroupFeatureI f => GroupFeaturePreference f -> Text +groupPreferenceText pref = + let feature = toGroupFeature $ sGroupFeature @f + in groupPrefStateText feature pref $ groupPrefParam pref + +timedTTLText :: Int -> Text +timedTTLText 0 = "0 sec" +timedTTLText ttl = do + let (m', s) = ttl `quotRem` 60 + (h', m) = m' `quotRem` 60 + (d', h) = h' `quotRem` 24 + (mm, d) = d' `quotRem` 30 + T.pack . unwords $ + [mms mm | mm /= 0] <> [ds d | d /= 0] <> [hs h | h /= 0] <> [ms m | m /= 0] <> [ss s | s /= 0] + where + ss s = show s <> " sec" + ms m = show m <> " min" + hs 1 = "1 hour" + hs h = show h <> " hours" + ds 1 = "1 day" + ds 7 = "1 week" + ds 14 = "2 weeks" + ds d = show d <> " days" + mms 1 = "1 month" + mms mm = show mm <> " months" + +toGroupPreference :: GroupFeatureI f => GroupFeaturePreference f -> GroupPreference +toGroupPreference p = GroupPreference {enable = getField @"enable" p} + +data FeatureAllowed + = FAAlways -- allow unconditionally + | FAYes -- allow, if peer allows it + | FANo -- do not allow + deriving (Eq, Show, Generic) + +instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode + +instance ToField FeatureAllowed where toField = toField . strEncode + +instance StrEncoding FeatureAllowed where + strEncode = \case + FAAlways -> "always" + FAYes -> "yes" + FANo -> "no" + strDecode = \case + "always" -> Right FAAlways + "yes" -> Right FAYes + "no" -> Right FANo + r -> Left $ "bad FeatureAllowed " <> B.unpack r + strP = strDecode <$?> A.takeByteString + +instance FromJSON FeatureAllowed where + parseJSON = strParseJSON "FeatureAllowed" + +instance ToJSON FeatureAllowed where + toJSON = strToJSON + toEncoding = strToJEncoding + +data GroupFeatureEnabled = FEOn | FEOff + deriving (Eq, Show, Generic) + +instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode + +instance ToField GroupFeatureEnabled where toField = toField . strEncode + +instance StrEncoding GroupFeatureEnabled where + strEncode = \case + FEOn -> "on" + FEOff -> "off" + strDecode = \case + "on" -> Right FEOn + "off" -> Right FEOff + r -> Left $ "bad GroupFeatureEnabled " <> B.unpack r + strP = strDecode <$?> A.takeByteString + +instance FromJSON GroupFeatureEnabled where + parseJSON = strParseJSON "GroupFeatureEnabled" + +instance ToJSON GroupFeatureEnabled where + toJSON = strToJSON + toEncoding = strToJEncoding + +groupFeatureState :: GroupFeatureI f => GroupFeaturePreference f -> (GroupFeatureEnabled, Maybe Int) +groupFeatureState p = + let enable = getField @"enable" p + param = if enable == FEOn then groupPrefParam p else Nothing + in (enable, param) + +mergePreferences :: Maybe Preferences -> Maybe Preferences -> FullPreferences +mergePreferences contactPrefs userPreferences = + FullPreferences + { timedMessages = pref SCFTimedMessages, + fullDelete = pref SCFFullDelete, + reactions = pref SCFReactions, + voice = pref SCFVoice, + calls = pref SCFCalls + } + where + pref :: SChatFeature f -> FeaturePreference f + pref f = + let sel = chatPrefSel f + in fromMaybe (getPreference f defaultChatPrefs) $ (contactPrefs >>= sel) <|> (userPreferences >>= sel) + +mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences +mergeGroupPreferences groupPreferences = + FullGroupPreferences + { timedMessages = pref SGFTimedMessages, + directMessages = pref SGFDirectMessages, + fullDelete = pref SGFFullDelete, + reactions = pref SGFReactions, + voice = pref SGFVoice, + files = pref SGFFiles + } + where + pref :: SGroupFeature f -> GroupFeaturePreference f + pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt) + +toGroupPreferences :: FullGroupPreferences -> GroupPreferences +toGroupPreferences groupPreferences = + GroupPreferences + { timedMessages = pref SGFTimedMessages, + directMessages = pref SGFDirectMessages, + fullDelete = pref SGFFullDelete, + reactions = pref SGFReactions, + voice = pref SGFVoice, + files = pref SGFFiles + } + where + pref :: SGroupFeature f -> Maybe (GroupFeaturePreference f) + pref f = Just $ getGroupPreference f groupPreferences + +data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool} + deriving (Eq, Show, Generic, FromJSON) + +instance ToJSON PrefEnabled where + toJSON = J.genericToJSON J.defaultOptions + toEncoding = J.genericToEncoding J.defaultOptions + +prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled +prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of + (FAAlways, FANo) -> PrefEnabled {forUser = False, forContact = asymmetric} + (FANo, FAAlways) -> PrefEnabled {forUser = asymmetric, forContact = False} + (_, FANo) -> PrefEnabled False False + (FANo, _) -> PrefEnabled False False + _ -> PrefEnabled True True + +prefStateText :: ChatFeature -> FeatureAllowed -> Maybe Int -> Text +prefStateText feature allowed param = case allowed of + FANo -> "cancelled " <> chatFeatureNameText feature + _ -> "offered " <> chatFeatureNameText feature <> paramText_ feature param + +featureStateText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text +featureStateText feature enabled param = + chatFeatureNameText feature <> ": " <> prefEnabledToText feature enabled param <> case enabled of + PrefEnabled {forUser = True} -> paramText_ feature param + _ -> "" + +paramText_ :: ChatFeature -> Maybe Int -> Text +paramText_ feature param = case feature of + CFTimedMessages -> maybe "" (\p -> " (" <> timedTTLText p <> ")") param + _ -> "" + +prefEnabledToText :: ChatFeature -> PrefEnabled -> Maybe Int -> Text +prefEnabledToText f enabled param = case enabled of + PrefEnabled True True -> enabledStr + PrefEnabled False False -> "off" + PrefEnabled {forUser = True, forContact = False} -> enabledStr <> " for you" + PrefEnabled {forUser = False, forContact = True} -> enabledStr <> " for contact" + where + enabledStr = case f of + CFTimedMessages -> if isJust param then "enabled" else "allowed" + _ -> "enabled" + +preferenceText :: forall f. FeatureI f => FeaturePreference f -> Text +preferenceText p = + let feature = chatFeature $ sFeature @f + allowed = getField @"allow" p + paramText = if allowed == FAAlways || allowed == FAYes then paramText_ feature (prefParam p) else "" + in safeDecodeUtf8 (strEncode allowed) <> paramText + +featureState :: FeatureI f => ContactUserPreference (FeaturePreference f) -> (PrefEnabled, Maybe Int) +featureState ContactUserPreference {enabled, userPreference} = + let param = if forUser enabled then prefParam $ preference userPreference else Nothing + in (enabled, param) + +preferenceState :: FeatureI f => FeaturePreference f -> (FeatureAllowed, Maybe Int) +preferenceState pref = + let allow = getField @"allow" pref + param = if allow == FAAlways || allow == FAYes then prefParam pref else Nothing + in (allow, param) + +getContactUserPreference :: SChatFeature f -> ContactUserPreferences -> ContactUserPreference (FeaturePreference f) +getContactUserPreference = \case + SCFTimedMessages -> timedMessages + SCFFullDelete -> fullDelete + SCFReactions -> reactions + SCFVoice -> voice + SCFCalls -> calls diff --git a/src/Simplex/Chat/Types/Util.hs b/src/Simplex/Chat/Types/Util.hs new file mode 100644 index 0000000000..fffdd24b9e --- /dev/null +++ b/src/Simplex/Chat/Types/Util.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE LambdaCase #-} + +module Simplex.Chat.Types.Util where + +import Data.Aeson (ToJSON, FromJSON) +import qualified Data.Aeson as J +import Data.ByteString (ByteString) +import qualified Data.ByteString.Lazy.Char8 as LB +import Data.Text (Text) +import Data.Text.Encoding (encodeUtf8) +import Data.Typeable +import Database.SQLite.Simple (ResultError (..), SQLData (..)) +import Database.SQLite.Simple.FromField (FieldParser, returnError) +import Database.SQLite.Simple.Internal (Field (..)) +import Database.SQLite.Simple.Ok (Ok (Ok)) +import Simplex.Messaging.Util (safeDecodeUtf8) + +encodeJSON :: ToJSON a => a -> Text +encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode + +decodeJSON :: FromJSON a => Text -> Maybe a +decodeJSON = J.decode . LB.fromStrict . encodeUtf8 + +fromBlobField_ :: Typeable k => (ByteString -> Either String k) -> FieldParser k +fromBlobField_ p = \case + f@(Field (SQLBlob b) _) -> + case p b of + Right k -> Ok k + Left e -> returnError ConversionFailed f ("could not parse field: " ++ e) + f -> returnError ConversionFailed f "expecting SQLBlob column type" diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 6002665766..0b7db12f94 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -42,6 +42,7 @@ import Simplex.Chat.Protocol import Simplex.Chat.Store (AutoAccept (..), StoreError (..), UserContactLink (..)) import Simplex.Chat.Styled import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import qualified Simplex.FileTransfer.Protocol as XFTP import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..)) import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 9a3b3b09c5..c6acf986a1 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -20,6 +20,7 @@ import qualified Data.Text as T import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig) import Simplex.Chat.Store.Profiles (getUserContactProfiles) import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Store.SQLite (withTransaction) import Simplex.Messaging.Encoding.String import System.Directory (doesFileExist) diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index c0956f2876..6f7e0b8cf4 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -10,6 +10,7 @@ import Data.ByteString.Char8 (ByteString) import Data.Time.Clock.System (SystemTime (..), systemToUTCTime) import Simplex.Chat.Protocol import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet From eacfc4aa8c39271dc8fc5cc746551a6b3dd4649f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 22 Jul 2023 13:25:13 +0100 Subject: [PATCH 12/18] 5.2.1.0: update simplexmq (fix slow queries and incorrect message join) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- stack.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cabal.project b/cabal.project index a9aa3f5d89..b747dff8be 100644 --- a/cabal.project +++ b/cabal.project @@ -7,7 +7,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 7e2b30945009e0cbe02a6b11a585595d3e4858b0 + tag: fdbfe0e8d159f394f6eb0f5168620da8694033cd source-repository-package type: git diff --git a/package.yaml b/package.yaml index 76b9e584aa..c4ac644da3 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.2.0.3 +version: 5.2.1.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 1d1036a334..3626f25eb2 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."7e2b30945009e0cbe02a6b11a585595d3e4858b0" = "0g0qlnnzzfiajpnnklxnk83p54mnr94483pj87rv1lchn4whfrpx"; + "https://github.com/simplex-chat/simplexmq.git"."fdbfe0e8d159f394f6eb0f5168620da8694033cd" = "0c0r14kzcp9rmc1nxypknaa93aczl13yxyaj1c0zrd1b17c2m8cx"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb"; "https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index ba68c8621e..f09042c7aa 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.2.0.3 +version: 5.2.1.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/stack.yaml b/stack.yaml index c5607a6a3f..e96175d919 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,7 +49,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: 7e2b30945009e0cbe02a6b11a585595d3e4858b0 + commit: fdbfe0e8d159f394f6eb0f5168620da8694033cd - github: kazu-yamamoto/http2 commit: b5a1b7200cf5bc7044af34ba325284271f6dff25 # - ../direct-sqlcipher From 7ebb763889f7c7c3425013f1dc279e754fd41e54 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 22 Jul 2023 13:52:04 +0100 Subject: [PATCH 13/18] fix hpack version --- simplex-chat.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f09042c7aa..c43adfb3e8 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.1. +-- This file has been generated from package.yaml by hpack version 0.35.0. -- -- see: https://github.com/sol/hpack From 065b932e1f57c899221a3d537ab8b6a4d556429c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 22 Jul 2023 13:57:28 +0100 Subject: [PATCH 14/18] update version to 5.2.0.4 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index c4ac644da3..c4dcecd981 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.2.1.0 +version: 5.2.0.4 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index c43adfb3e8..bba4d69689 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.2.1.0 +version: 5.2.0.4 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 65c6c63024c6ff503ca1c7d488b459b6ffcab65b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 22 Jul 2023 16:26:18 +0100 Subject: [PATCH 15/18] mobile: 5.2 android 134, ios 160 (patch for database performance) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 52 +++++++++++----------- apps/multiplatform/gradle.properties | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index cb908cbab3..2a5735990a 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -137,11 +137,11 @@ 5CE2BA97284537A800EC33A6 /* dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CE2BA96284537A800EC33A6 /* dummy.m */; }; 5CE2BA9D284555F500EC33A6 /* SimpleX NSE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 5CDCAD452818589900503DA2 /* SimpleX NSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 5CE2BAA62845617C00EC33A6 /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; platformFilter = ios; }; - 5CE381D72A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381D22A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI.a */; }; - 5CE381D82A69AE88004FB9E1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381D32A69AE88004FB9E1 /* libgmp.a */; }; - 5CE381D92A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381D42A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI-ghc8.10.7.a */; }; - 5CE381DA2A69AE88004FB9E1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381D52A69AE88004FB9E1 /* libffi.a */; }; - 5CE381DB2A69AE88004FB9E1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381D62A69AE88004FB9E1 /* libgmpxx.a */; }; + 5CE381E12A6C103D004FB9E1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381DC2A6C103D004FB9E1 /* libffi.a */; }; + 5CE381E22A6C103D004FB9E1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381DD2A6C103D004FB9E1 /* libgmpxx.a */; }; + 5CE381E32A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381DE2A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd-ghc8.10.7.a */; }; + 5CE381E42A6C103D004FB9E1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381DF2A6C103D004FB9E1 /* libgmp.a */; }; + 5CE381E52A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE381E02A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd.a */; }; 5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; }; 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; }; 5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE227DE9246000BD591 /* ComposeView.swift */; }; @@ -415,11 +415,11 @@ 5CE2BA78284530CC00EC33A6 /* SimpleXChat.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = SimpleXChat.docc; sourceTree = ""; }; 5CE2BA8A2845332200EC33A6 /* SimpleX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleX.h; sourceTree = ""; }; 5CE2BA96284537A800EC33A6 /* dummy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = dummy.m; sourceTree = ""; }; - 5CE381D22A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI.a"; sourceTree = ""; }; - 5CE381D32A69AE88004FB9E1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5CE381D42A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI-ghc8.10.7.a"; sourceTree = ""; }; - 5CE381D52A69AE88004FB9E1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5CE381D62A69AE88004FB9E1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5CE381DC2A6C103D004FB9E1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5CE381DD2A6C103D004FB9E1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5CE381DE2A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd-ghc8.10.7.a"; sourceTree = ""; }; + 5CE381DF2A6C103D004FB9E1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5CE381E02A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd.a"; sourceTree = ""; }; 5CE4407127ADB1D0007B033A /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = ""; }; 5CE4407827ADB701007B033A /* EmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemView.swift; sourceTree = ""; }; 5CEACCE227DE9246000BD591 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = ""; }; @@ -501,13 +501,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5CE381E22A6C103D004FB9E1 /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 5CE381DA2A69AE88004FB9E1 /* libffi.a in Frameworks */, + 5CE381E32A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd-ghc8.10.7.a in Frameworks */, + 5CE381E52A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd.a in Frameworks */, + 5CE381E12A6C103D004FB9E1 /* libffi.a in Frameworks */, + 5CE381E42A6C103D004FB9E1 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 5CE381D82A69AE88004FB9E1 /* libgmp.a in Frameworks */, - 5CE381D72A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI.a in Frameworks */, - 5CE381D92A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI-ghc8.10.7.a in Frameworks */, - 5CE381DB2A69AE88004FB9E1 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -568,11 +568,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5CE381D52A69AE88004FB9E1 /* libffi.a */, - 5CE381D32A69AE88004FB9E1 /* libgmp.a */, - 5CE381D62A69AE88004FB9E1 /* libgmpxx.a */, - 5CE381D42A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI-ghc8.10.7.a */, - 5CE381D22A69AE88004FB9E1 /* libHSsimplex-chat-5.2.0.3-9DDzk5dukPe6oTQ1CZlGPI.a */, + 5CE381DC2A6C103D004FB9E1 /* libffi.a */, + 5CE381DF2A6C103D004FB9E1 /* libgmp.a */, + 5CE381DD2A6C103D004FB9E1 /* libgmpxx.a */, + 5CE381DE2A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd-ghc8.10.7.a */, + 5CE381E02A6C103D004FB9E1 /* libHSsimplex-chat-5.2.0.4-HUgQMHMGu1K8FDeUwC5hCd.a */, ); path = Libraries; sourceTree = ""; @@ -1478,7 +1478,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 159; + CURRENT_PROJECT_VERSION = 160; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -1520,7 +1520,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 159; + CURRENT_PROJECT_VERSION = 160; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -1600,7 +1600,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 159; + CURRENT_PROJECT_VERSION = 160; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1632,7 +1632,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 159; + CURRENT_PROJECT_VERSION = 160; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1664,7 +1664,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 159; + CURRENT_PROJECT_VERSION = 160; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1710,7 +1710,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 159; + CURRENT_PROJECT_VERSION = 160; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 891c4308bb..77cbc79b93 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,7 +26,7 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 android.version_name=5.2 -android.version_code=133 +android.version_code=134 desktop.version_name=1.0 From 80bd734cc1a47dfd94bb22ccd90c30ca1cf3728d Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 22 Jul 2023 20:08:24 +0100 Subject: [PATCH 16/18] blog: v5.2 announcement (#2748) * blog: v5.2 announcement * remove extra heading * updated * update blog, images * blog readme * readme --- README.md | 4 +- ...lex-chat-v5-2-message-delivery-receipts.md | 156 +++++++++++++++--- blog/README.md | 26 +++ blog/images/20230722-filter.png | Bin 0 -> 179060 bytes blog/images/20230722-quoted.png | Bin 0 -> 286219 bytes blog/images/20230722-receipts-full.png | Bin 0 -> 580656 bytes blog/images/20230722-receipts.png | Bin 0 -> 485556 bytes blog/images/20230722-search.png | Bin 0 -> 158251 bytes .../src/_includes/blog_previews/20230422.html | 4 +- .../src/_includes/blog_previews/20230523.html | 2 +- .../src/_includes/blog_previews/20230722.html | 29 ++++ 11 files changed, 197 insertions(+), 24 deletions(-) create mode 100644 blog/images/20230722-filter.png create mode 100644 blog/images/20230722-quoted.png create mode 100644 blog/images/20230722-receipts-full.png create mode 100644 blog/images/20230722-receipts.png create mode 100755 blog/images/20230722-search.png create mode 100644 website/src/_includes/blog_previews/20230722.html diff --git a/README.md b/README.md index 904f1dd2b8..a6cb204193 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,8 @@ You can use SimpleX with your own servers and still communicate with people usin Recent updates: +[July 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md). + [May 23, 2023. SimpleX Chat: v5.1 released with message reactions and self-destruct passcode](./blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md). [Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md). @@ -337,8 +339,8 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A - ✅ Message reactions - ✅ Message editing history - ✅ Reduced battery and traffic usage in large groups. +- ✅ Message delivery confirmation (with sender opt-out per contact). - 🏗 Desktop client. -- 🏗 Message delivery confirmation (with sender opt-in or opt-out per contact, TBC). - SMP queue redundancy and rotation (manual is supported). - Include optional message into connection request sent via contact address. - Local app files encryption. diff --git a/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md b/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md index 7f4514e98d..42c08245f8 100644 --- a/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md +++ b/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md @@ -2,10 +2,8 @@ layout: layouts/article.html title: "SimpleX Chat v5.2 released: message delivery receipts" date: 2023-07-22 -# image: images/20230523-reactions.png -# imageBottom: true -# previewBody: blog_previews/20230523.html -preview: TODO this is a placeholder for the release announcement +image: images/20230722-receipts-full.png +previewBody: blog_previews/20230722.html permalink: "/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.html" --- @@ -13,26 +11,144 @@ permalink: "/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.html" **Published:** July 22, 2023 -TODO this is a draft of the release announcement +**What's new in v5.2:** +- [message delivery receipts](#message-delivery-receipts) – with opt-out per contact! +- [filter favorite and unread chats](#filter-favorite-and-unread-chats). +- [more usable groups](#more-usable-groups): + - view full original replied message in info. + - share your address with your contacts and group members via your chat profile. + - search new and existing members. +- [stability improvements](#stability-improvements): + - keep your connections working after restoring from backup. + - restart app (Android) or reconnect servers (iOS). + - more reliable switching of the receiving address. + - more stable message delivery. +- other improvements: + - [better disappearing messages](#better-disappearing-messages). + - [prohibit message reactions](#prohibit-message-reactions). -What's new in v5.2: +**Platform evolution:** +- [problems of public groups](#problems-of-public-groups): + - why not all messages are received. + - how to cope with these problems. + - when will public groups be more usable. +- [what about read receipts?](#what-about-read-receipts) -- message delivery receipts -- improvements for groups - - view quoted messages - - share SimpleX address with your contacts via your chat profile - - search for group members -- feature allowing to fix connection encryption (for example after importing old database) -- mark chats as favorite and filter chats -- restart/reconnect -- chat preference to allow/prohibit message reactions -- disappearing messages improvements: - - send with custom timer when allowed but not enabled in the conversation - - increased timer limit +## What's new in v5.2 -## Future of SimpleX Chat groups and communities +### Message delivery receipts -TODO + + +Most messaging apps add two ticks to sent messages – the first one to show that the message is accepted by the server, and the second – that it is delivered to the recipient's device. It confirms that the network is functioning, and that the message is not lost or delayed. SimpleX Chat now has this feature too! + +In some cases it may compromise recipients' privacy, as they show that the recipient is online, so we made sending delivery receipts optional – it can be disabled separately for each chat profile or contact. For the new chat profiles this feature is enabled by default. + +To avoid compromising your privacy, sending delivery receipts is disabled for all your existing chat profiles. The first time you start the app after the update, you will be offered to enable them for all [visible profiles](./20230328-simplex-chat-v4-6-hidden-profiles.md#hidden-chat-profiles), and they can be enabled later via Privacy and Security settings. + +### Filter favorite and unread chats + + + +You can now mark your contacts and groups as _favorite_, to be able to find them faster. With filter enabled, you will only see favorite chats, chats that contain unread messages and also any unaccepted group invitations and contact requests. + +### More usable groups + +Active SimpleX Chat users know how broken the current group experience is, and that we plan some major overhaul of the groups protocol – more on that below. In the meanwhile, we added some simple features that make groups in their current state a bit more usable. + +#### What is this in reply to? + + + +A major problem is that you can see replies to the messages you've not seen before - this would happen both when you just join the group, and didn't connect to most other members, and also when other new members join the group and they didn't yet connect to you – so literally all the time, and the bigger the group gets, the worse it becomes. While this problem cannot be solved without major group protocol changes, at least there is now ability to see the original message that was replied to via the message information. + +#### How to connect to this member? + + + +To simplify direct connections with other group members, you can now share your SimpleX address via your chat profile, and group members can send you a contact request even if the group does not allow direct messages. + +#### How to find a member in the list? + +Large member lists (and also the long lists of contacts, if you have many of them) become hard to scroll through, so now there is a search in both lists. + +### Stability improvements + +This version fixes many long-standing problems with the message delivery, failed connections with group members (that also contributed to group fragmentation), and reduces traffic in groups (beta users experienced a traffic spike because of this fix, but it doesn't affect the final v5.2 release). It's not the end of the road to making SimpleX Chat as stable as mainstream messengers, but it is a big improvement. + +Please report the cases when messages are not delivered – delivery receipts should help with that. + +#### Messages failed to decrypt? Problem solved! + +Previously, a growing number of users had the issue when after restoring the chat database from backup, messages from some contacts failed to decrypt and were showing an error in the app. + +This happens due to double ratchet protocol protecting the integrity of end-to-end encryption after the compromise - [post-compromise security](../docs/GLOSSARY.md#post-compromise-security). The protocol logic does not allow to use the old version of the database to decrypt the message. + +v5.2 added the extension to the messaging protocol allowing to negotiate the new ratchet keys in such cases - both with the contacts and the group members. This requires a user action, and it resets the security code verification status for this contact or member – you need to verify it again to have the additional protection from [man-in-the-middle attacks](../docs/GLOSSARY.md#man-in-the-middle-attack). + +The negotiation of the new ratchet keys still happens via the end-to-end encrypted messages, as the protocol has two layers of end-to-end encryption, so it cannot be compromised by the messaging relays. + +You may still lose connection if you or your contact changed the receiving address after you made the backup, so make sure to make a new backup after any receiving address changes. + +#### Reconnect the servers + +While v5.2 solved many message delivery issues, there may be some others, but they are usually resolved with app restart. It was difficult to fully restart Android app, as there is a continuosly running background service for notifications that doesn't restart with the app. Now Android app has both Restart and Shutdown buttons that take background service into account. + +On iOS you can now pull down the list of conversations to reconnect to all relays without restarting the app. + +### Better disappearing messages + +You can now send a separate disappearing message if the chat preferences allow them, but do not have any time to disappear enabled – this applies both to groups and to contacts. You can also set the time to disappear up to 12 months. + +### Prohibit message reactions + +While most people like message reactions, some conversations make them inappropriate - you can disable them now. + +## Platform evolution + +### Problems of public groups + +As I wrote above, the major problem is that not all messages are received by all members, or, at least, they may be substantially delayed. Additional problems are various scenarios when the list of members gets out of sync for different members. + +How to cope with these problems? + +It really helps to only use one link shared with the members to join the group - the one created by the client that is most frequently online, ideally always online. This is sometimes confusing, as any group admin can create another group link, and share it with the members, and if this admin is not online, the new member won't be able to join. + +We will add new group features to manage fragmentation - there will be an option to fix the connection with the member that you failed to connect to by passing the link out-of-band. This can be particularly helpful for stable groups of 20-50 people where it's important to see all messages. + +In the long term, the only way to make groups usable is to move to a new design. We considered several options. + +#### Why not hosted groups with MLS? + +Initially, we considered the design with the dedicated servers, potentially self-hosted, that host groups. This design would require adopting MLS (or similar) protocol for group-wide key agreement. Unfortunately, this design is not sufficiently resilient and easier to censor than decentralized design. Also, MLS protocol is very complex to implement, requires a centralized component, and reduces forward secrecy. So we decided against this approach. + +#### Why not fully decentralized groups? + +We also [considered](../docs/rfcs/2023-05-02-groups.md) rumour-mongering protocol, where all members are equal and participate in message dissemination. The problem with this approach is that it adds a lot of traffic for all members, even those who mostly read messages. Also, it still requires establishing a fully connected graph, and with large groups it becomes prohibitively expensive and unreliable, given that many members join public groups for a limited time. + +#### Members host the groups + +We are now considering a middle-ground - the design where the owners and admins host the group, synchronising the state between them, receiving and re-broadcasting the messages between all members. This puts a higher burden on these members, but these clients can be hosted in the cloud, and also group owners have a larger incentive to maintain group integrity. At the same time, this design is better for the rest of the group members, as they don't need to establish connections with all other members, only with a limited number of "hosting" members, and it also better protects their privacy, due to the lack of direct connections between most members. + +This approach avoids the need for a group-wide key agreement protocol, as hosting members are expected to have access to all content anyway, so pair-wise ratchets are sufficient. At the same time the content remains end-to-end encrypted, and protected from the outsiders. + +This approach also simplifies moderation - the message that needs to be removed simply won't reach the members before it is moderated (in case of automatic or policy-based moderation). + +Discovery and content search in such groups will be provided via a dedicated discovery server that will participate in the group, provide an always-online client, and also automatic content moderation functionality - a possible approach to moderation is [described here](../docs/rfcs/2023-05-22-groups-moderation.md). + +We really look forward to your feedback on this design. + +### What about read receipts? + +We have an approximately equal number of users who ask us to add receipts, and those who ask not to add them, even as optional. + +While read receipts provide some convenience to the message senders, they introduce a lot of stress for the recipients. + +As one of the users in the group wrote it: "The existence of read receipts in other platforms is exhausting and is often a source of undue stress. I have to make a decision to read something and let someone know that I have read something and decided not to respond or merely didn't have the time to respond. The outcome of that is a complex social negotiation with non-theoretical social fallout as a consequence. All in all, it's an invasion of privacy of being able to read things at the pace of the individual as opposed to the pace dictated by others... Most people don't need a read receipt, so leave it to a group of individuals to decide if having read receipts make sense to them for their workflow". + +Also read [this post](https://neilalexander.dev/2021/04/09/read-receipts-typing-notifs) about the damage from read receipts and other invasive features, like typing and presense notifications. + +There is also no discounting that the presense of read receipts functionality, even as opt-in, creates a social pressure to enable them, with the same consequences – there are many scenarios when they become non-optional in some relationships. So many users belive, and we share this view, that it is better not to have these features at all. We will be re-assessing this view. ## SimpleX platform diff --git a/blog/README.md b/blog/README.md index 56c39589a2..129ba2c80d 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,5 +1,31 @@ # Blog +July 22, 2023 [SimpleX Chat v5.2 released](./20230722-simplex-chat-v5-2-message-delivery-receipts.md) + +**What's new in v5.2:** + +- message delivery receipts – with opt-out per contact! +- filter favorite and unread chats. +- more usable groups: + - view full original replied message in info. + - share your address with your contacts and group members via your chat profile. + - search new and existing members. +- stability improvements: + - keep your connections working after restoring from backup. + - restart app (Android) or reconnect servers (iOS). + - more reliable switching of the receiving address. + - more stable message delivery. +- other improvements: + - better disappearing messages. + - prohibit message reactions. + +**Platform evolution:** + +- problems of public groups problems of public groups and their future. +- what about read receipts? + +--- + May 23, 2023 [SimpleX Chat v5.1 released](./20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md) What's new in v5.1: diff --git a/blog/images/20230722-filter.png b/blog/images/20230722-filter.png new file mode 100644 index 0000000000000000000000000000000000000000..5845c7eb3b4a4aede22318225bcb2003bd22d3ae GIT binary patch literal 179060 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

*7G^R-yWaiG*NS)$!-@TdMclp+t-}oF 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

w1SLL>M)Bxu%qWRGo_(L2nP*SvXUgSstG}i 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 Hr-UW|AisaV literal 0 HcmV?d00001 diff --git a/blog/images/20230722-receipts-full.png b/blog/images/20230722-receipts-full.png new file mode 100644 index 0000000000000000000000000000000000000000..f1eb2be35f0cbe5191debc480b3e9e15d2a958e6 GIT binary patch literal 580656 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

;xW(1j_ z@(h7e-mmr$5bM}#J5hW_9zC@E1U*cHnB6`~@Y)IY$-9smwKLijjB$KB^2a1ZTLfIV zoxxlRS#*gNE}gmdr%$On5gI?^hSUJ!pH9(r^6(RzAOxot(Irqjii;P^u%hWxz|Uem z0L3G>tSNV3P{4LuUnZ^g&ghik>>Ah5&cb{#SaIvnGbcW)C(`$wvSyFA%*;;|3=|Ke zF3qq>pXqfx5*wSKOvcbJk7AeP@}E8JNZAa&d>W0ljkv-xfM8tQu4IJMGT%P{9gw9W z0%d#iJ=+=RRZ-MH$JipJH5JO?1Vku;(6@|50m*{99q8W@vF+8wl5LJMR7Hr_$-r=k zPy(!bz-#c27h%yV>#|%$6&a`E15?lUBYF4VTU*9B8J3(E%027=Iw#IZ?= zYeH;LXxD`R==h;WMD3@cY~y=D24@@K{Bp!TscJRBrcx*&>^0 zMU6`wl&2i^x0q%}6j*6_^r2MA(h=1`7XHe4MoPSEjU#IcsINY}pg9D-^|locD&Sjt z!>+Pnl(<>nyS{jdN?2~MazUPkJVJL9hl1ZN)F#AYE1>0g#d6_daf^=>YBhD%UEkrT z?=O9;k6yI%SjGjL3{i#0(-GdUfSy*roy@uYVt&!Bqm`~;^DQ_~a{^_oXGLfj{$y>=7m9<@>lDjIl_@L=ce&HL-nvbE&bNg*d*(z8%#Ob9Y?WR#}sIPK{m zc-tI+q!K0KWTmS{@IWLoBpx{9<-KHL4Q0rNo%!4-dC8mex*r?1prTgF$o|SZw!6W7 zO#SKJFT=k;fb+^PI3JuRC(yE%MLbah1=zxEvk-KMGPY-=G9C)EHN2(-AAnC4vhjEE z6FCh4yI@Fk*^$oRo&)_EIl+F<{GI`y!;`kJ`=t>&CVG$=2VKh2N-?&su%$STH>K)z)Lcwk;diClqATjaP z5PG+Li}QsgbT7N{bf}_%#%sN79`_At8vm+~Rf96|YK=|NJST*uoL$cDL#;)mf<@1S z1hMTy%3VMST9MSiZ0GfyKX7uKic~bK+o2YOk74kEhfc7{y@)$2CwSX^!RvBd2dMpbLKDQ$H%67Qt4;5Wh+e7x;&TCr4 zL7gm-z_zC>CU<;bQ|lo#r@U>J1Z+aQvEGTdHrkA7@9vy8Qgl*f+z*!?T0JnhDBQ^- zy(EAAP0i2#=t>&Yz_=DR`{L>iD8Hak6e5h>;dxs_at6ELvSVvlfApo`!S*u)ZGB-0 zz!t>E)%9%O<^z32+fL?g@*Xcu*AVNLMjP5h^fC8hy*=);`ekzoIp@v>n^G5?mB3FU znf&05UE&xT@#EEJ?T1OFZmWJ1(dTdAJiY4>T7=#ukAmM3Gki#Q<^vXT&%HLNW%Q8O z&p{trkl^)IwOm3QCr@%=@r;#l#*%FxJAZrB>S>rG*1A5q7BE0YiHry?nhI5BJbwDx z``HAS?)5l6xk%3^omTHoDbI$fhxHHSRIYh9JF7@WFJ8*%Wb&R{ti%zkQLl18gnaCs zNQSNe^&NuUOSkb2^_P=L!QMAoT7jHn000G_0iPXUD3v3I({KHzzjq`nZ$xf!tL37wN$OKro4&5@ z-F8r>0n_YSoR)P4)l6HmkmS)ym;~%~&{mnq{4VQk(6xqeG(;?r4vpOzn=3G+aYYHu`e@-qWDUbw?kS z8{g~?{(qMC`AvWHgC_wG(u2}aVdqpQqK16#Se3-~Z{i^icI@H+oulG;!6+bncUIwRO>7}gJc_!&y zPEK|w_CzI>>Qsc2Cu{lY4Xk>vwZC+5bKUmoOP|(})p(*@o^{m>ZO7PIzQJvSUefPj z-p39f7!s++sV-XbLKT9YUil)3y2RY?KI82X1DGd&WhD&)!?Y=llJcH$ytaD z#^z8e`;g$|k|`FYRdUa_CQc*%Z%yBlzj)|UP-dQOdmI)2tk$Vy3NK3l^EcQ@#oVGdH_cd%ISgUeAN> zVz+Do94KGop*i1M@2iZr2mIkPw^eMo+Y#ErD#2&kv_qn=zuH#5wvw1-}eaRfPnsDTU_Q)XD zuy6JL-)11r)v5drXz4F=h=x<}N9CwyQ-y$>jK?)8W^yZHWlXDznF8E?n5Pc@ ztQ7QhI{Zr>W_PKJ$KeAHw;06g`wxYxX3Gp(6FI^2r32i?jZPe({saA=ZIs7`;{NRcwXmT6e5 zGamg;jf%*FXJ@B@MS6HSb? z`3}FOZ9gv^QivQ*(J_ASdhoHnkK^6`zP#S!kYX`IMg~@!5!k8h*0tnByjcz&UO#Rb z@Le)3rhB-X&s3@%WcJAS@?#Re3Q&KdOt1CHD6MOW(+Z%A#I5py7-jQ|%9`II*fvas zCGgmbuT4DG6@i?-$*6OWC-UnDLMIX+3)7FaqeKlAjkq3uFbnD5IV_h-bSJsUe<)Hn zh`yUXTwD7#eRk{q1tX<5;A`hOXE)X$Jkc%obfu8T3C_Z~2@09OF@IRjSRl|P)P5GHiBL?>7(o*!?t?y)IsJ9?4+szK?wb+x1! zV$Vydu#sLRgJBzoGQ0qhA-^YA?E$_!G5p%{P0;p4&O5>qtCcIVlivz)G}-uj-`cf( zj!coo=!&x18ANh&JIH8H&2V(al#F;Kj)H&s7bcp{R_is~Ibf_>+qo%6y0BKPLv+R` z8|@6auP0&MHDjW7)>o9#gID@|v+v2jVlZzGyvRPEyu9-v81BOUob+BRC6`=HEmg>M za;u{GxRtSP?K3GY?4H5E(RvW%)8On#teru*ZwmU1wbW?ihV8D8*)|Ke-aPdXAKLsb zP69T(@_M@`q%}2-&O59NuUr4h;=bQFb*yltVzaE-kIUi#S3hLkVM*VWrFqP| z9Y_KlpeKrGR490RUlmN}Szh2j_d+L{KqEb)W>^>4u^sM{Z+Uu-F>?Iu{GftgyKJyT zY{|0DZt>>XhvAI#`k^#~#q;yg+HD{F;ZN|`L!$#^$sZL&%jTh61OMGkrKoF2k7A{UqW-ex9KC#@3Dc!nIl)n?X*Rbw^9 zu&AmPK8tKc`z7vA*ZH0XwBxM7F*NqemdNA^I0a@1oFd#(=k@j?L+1->Mha+Nqz(3k z3$T(4m@|>dvAyEX6|joT@LOl=B2wTlGhyw#sWgY~CQUR+$BJsC;VALc@Nnj#b@;E&w@>*8_1iW3#v z3t9{(0sw+n0$Gi&HlJaeZQEFxadR&L(SyOA30wvi#%b3Gpn~*QoN=J4*>TQtA@7P+ zuXc=-K-~27w6sBHW)lf1Hqk&>+>wY+13b|FrD4BQfBCi445n*G_GdjQCePyA$^6Io zUsc|xCnRJ!G;x0&M-fYQ-PSrkBwacfGT~-TeaB+*@eo}UwHqZc`h6llKYvyMCp+&L zn;{K?caD{v9XAFM2f*j!6vmcRoym2i=bw7;z`9681i_Ps?0o%lJCe$twLSDIPf=}x z>wMpaRjDgMO50nstU=eD?TWhhb$g=wx3<=u@O;`~l}vz9tZp_zOr(07a&o~<$j$5{ zKEAO?qP|>!U7?nIC)@nP#;e3OD4H!3#*VPSC?n^<(9q5DG;`95mI%arqwV%crEOCL zyO&`osIqj?!C3#UWuC#LP_jREAd5_a7r@d<%4ZLV6FsfB-Nca zst<-;lPprmLaw*A9^+R&2Y`HLy(zs+E^_LZiO9KqYUi8m^7L3FZ*YO$WVcRanCLYr zGc(%fog4I!)2JaOE31t8`pOwLqtA)U7mu5W`q`WB{^r=+Wa2p+l6&(sJnvxvMls-U zh?EynrdQ*aRJ`0gs~O4Hs$2VkN%}!uUS8MqQEFw$(5xiz)@R{jFF}n~R{H9Ad6C{J z#S|I(Z6dEcs-WG-8T0YF4-<)IiK?jkR`~k6@)8oGM>f6D749M@Ic1)+%a(7~OJ&Ym zAw8sSMO+T90Q8kZOL5rmuKXCeCNvQC?uX~qw#Xt2lh*g#ker`}H3bndHm?PA-gou8 z&=gxh_)xv!{o#+z^7wPa;c}+x@lYO}YzO6g8vEtz_%~7+V#3e1SifA4WAZOORdKMg zvE$=kt!f|626kzifVr=`3nyS(ko&92%uN0KLnfXBce}S@yZWEchVSNLX` ze@-%FImdiRi1l{m6}II{I(HsLDG6`M$=MLcf!Es0uxgIy-i;z!;G62S;hB*vY4Ne@ zpnHK2Fp=whZftgX(oA@}SXqYcZ}f}&sKQ-D@OBM-&s^>saZJMF=QeSBY(TN24+xxC zT_zI*VhUjiydT$^Vfi=laS_NcF}Ks;TT=h5%uM80sBu$2((bkF4LFi<^bDyC{>w=1 zY1ke>_xT4GydBIPqa8zz9gAS~EnIWh7oXNyF0SFR4<&gOCBvDbMu+{3Ds`(tjgGzM z&`r!)y|x$Ja#$T5Vd(Q*UVGa*nP-#hkT0LLXtKYl++|qgMh&1uY^Con9}`sN_7~iu zjT!BzuD4^-cVwdkeLf=udT#T>w!wrP>0<8=ma#?CUDrd>GD8b#cTx(XMvk5fm48XB zwO?%CBa4ZNc|~tx>vpulDHH+wP1VHSoiIr@Oh8945PfxgBqc2^jRekPC4HAn&}Fw4 z>F~*orMQ|}LQiE-lpD>U03}%vZP7(A<8!CY-i(EXg=E<^pS7UMyq}8_?0h8b22?cs zX1}4bqCy!>pG0ihcLz22N$_x89bf63sh@yWn7E0tvHiPjG%UxUv^2_2Lza^q>xULB zCc56Diidrbt(W@Tu2!(?{~54s_BZHpZ$_1y|nNy2gc-A>)~D8iADQX`mduX6wyje z;(VSn$NZd}o*mPa!j|`*AlY${+){YvJv%%tQlg8r^NjLb4VwSvl>ek|` zj)HU|3Cs-Vdj5!w>r(m~?5{*4u$-<;8*wDm7z_qO=Bhy2={ib+0-w|aask0k9KMUk z#n~@O0y;gok+iXs5HZrB+%e3s{C)uE>llk=17g*M-2J@yX)x&3dv zqR8M@^3LF;x8OR1^mOH7UmP`*Rx~{^77iWCc_+0q;?UGHjAJKw(IvN|xu%L_46vvu zeke9DeY9xh_cbY(&hNa`M8@V|f4kw}BPBk-B*8`=^@G&Q`q!$lg?G5zCIe09v5T9> ze#cU8PB$4cd~dJ%8OCIs6;*;BaCwg3$3snqg=L;NvC}fPlxTpEyDdMfc10}%xeHT8 z$Btz`Kp9yO$w}}$MZdf`evM%{mLbu;!3nFF-MA7ZtTUqkg!iba!~x#T{DJ=@lly{Xs246-F$VA?Oas{n_hLk9C&>0jahk1`(1u#6MAP*ekSua1t$|P zWSC2%P%#BM=AN}^Z~T;_@!C{@p+y|h^`W+9K5g_-5=Xi<7R7>tA9d#q=d+ur-J{l9 z+)*)>hixpgK!+LU3XC0=k_TAlXHlH}26q$5 z?J-n?L${@sks>$Mvr3JY5jumA@#W?#-Hzw=wH+Il(UzsSuNrp^Ev>q+K3**~LPrYH zjIFEYC2wO=5kB%ej8E6|#}W}{e_j{#kWnyJa34@nJR{7pWbORTVA=a_v?8z5LeHD;LG=z*E$09TjY|`vj?)l}@ ztW2Bdd)U%aXu!9HK;VUXWmuAMgzS=P9}j&IR_SN#xvjNR#iVX=&+P*fou>pSN2yyz zo+tsba~L&JYekg#Sqf#C)9-Uq_ZaMBcf}WF8!>)Sj85x1)zEsuC)L`;t0!kDun~QG zR#PX=v#!M1eDt~d3huKtU&?T;m~!rBH-|LdL81)4H%5^I85u`*LpK;itxx%E7pSwi z&W7<-gh9aY3}gp8i^U0Mt9;ZMTfB7)eNY&K&)2(~ok-RSE-fvs89!{@+SNYjeFQ-q z%Oag`9-clv$G{hN;5CoSd5e)4{uN6d<|<~*wkwLPu!|ptM0`i~6sseY?Bhq?F<~y#G}|2oYO;`?`(6PeZ5J}s8Hu~+K{%n#X5Y7Guc(h zUe;9W?vhzDhz{-hSg%h{d-f1owkb7<{$Z+UjOSC>fi(2%CQK}6NxKv_(6&t@^*+j> z0-jgeH`%9SS{;TDMHQ4yJnOs;cOzj*+~c;Ln~u5QOlF$oV=hj1J#+o-bSa8D^lftT zk2@I_L>bS$vC?gEXWn4+QcvgD8L;h2x9`NbHP0m%=i&QZ}u>KD>Hbp+NA6RyLXQp_u()I_#ITwu3L&V%a^iV2kqMmGY>+T9fQeG@=3*BAi{s){C38PiHR@d z)E1vIzrJg0xORfLKJVexml~lu3iaAAE*fU9Uos^=bUU1zu!9rkON%>&8HIom)-G{) zrP=1*#>GwC82t5c8kri6$jw&L&Q6v?-IIu6&;4s+pFweF@msjD$G4%WeZp@YRz+=c zzb-+?GAf^aIheU0>T-!e1(&C~LBM~KZ6!y#v2kyGzqFFj>xq&!!0O2Nz$jI;_BP*O zQd;2M$%aCA88~$r=ku3iKgO@ktKURn2UR&xQ4>oOhFKtEcCJkz5P2$sMI&X^aN*<* zww)Yn-8cgpVSn51tA-3Dgx_8sKjIYqX3L-D446ROe&(~q4TPO29M)!;*K|i|C&i}T z*#d+G(L4FGxVjdc0NCy;o<$jtY*HW6%OQs>M+oW@kA{s=pm@j%8z@_6@PH$Y=G&1S zxzgpIi&R`Eil88eB3`^bLB7mE!s)%Pc{GZxSdN0CoosXnlEiOz+LZN1h>Cp{6eOu4 zla9!kNLi&~^42=V^I}47%4vm&O(G_t%hh};@#-8Q22(S4S2t{o8C12m5iz4<>e!Y0@ zGcXmt{GR%JkhOA-7Jn?YD%g#Ty)oz0VDCtr&lWADBk*2mEY6I;2}A*LB->H>#_Bd2 zaS;A_`doBS3|XCTI>Q^Q+3#DtfUoQR$_uF5f_x>F^p2H8n@pP`NsaDby5PxY=MK6f zG%fXA?+$YDgi`~)pW9`eAxCz~L=k3G`hK)=Lu3X#6^**QvrPx@7pn3-(9w=fNIP_c zuC_YtF=S7@dx*i&c!$%vsDW83f+-eJtR}vO90dCpK zEY~Z~Ny)}7dZ;?^sGB!3+AYI=kGaGaR~121Pku&1cF;|cL)+38h4lA z?j9hxyEfKnAV3J#xVzI#|K58u>pwH=eCYeJ*R6BwRPEY(pFDBoL(BMW^J4PawTDX_ z{@ViHbBzU;e3ch{JCaYTKt4>mj(5mQ5`a>syg;Uv@)qli&YXrqXg5WPo$KMBcj3Qj zrME@};Bk>0e$*|^knLpS+UFo!JO^{BK4xYzcE;ISE+XYTGb|u=K178AlXB&|;^dU9 zU?Ho#S*Cou^V-VN#;OM3@p6Xw%4a82n&%oUM5CV15-P+m?Z0#*mddd73hdx2#k~mJ z8a!n4UMIncoJx+K&Fyt75WrH8xK6ysycxGt%i3R76&im1q2K11ts=2#fXDlx%bh~y zco<8kxKvg8pieCt+l0AZTA z9&ANys7E|YE&Lzj1wMK-oQ+bk}Sv_#Vq{K5+1- z_@?AwOU6N*IJ|?W+(mOcFG&iMg&h}fUUNRSo+p(f%dQUV%fV-gh?C+j z_zI(1Tn5#gj}=3s^PrPI-t=_I`ktP5h|=7Z0ac-ypU@4w*-`fAqSXU6mR2S=brXM% zcRieaSL(Yvdyq#%iv?XhgVH!>FRMv)mz&sWIZq-5d$2xN$GbG%$3 zA7(&9xQ?Rq9rp*5ZE{}tZN8i?U}K1BiHO@7#xT~-lA8&+94(aJTdLb?4$s+22O!Oc zxCg_dVEG%VL^u;LTv|5PO6p-T3zUoq-DM4FsxY{WZ8SH2^TllF{PXb|M`Wz^<~Ctm4rt|QOR_dMGk#$X8_^K>gd z^sM**uW)O>MR^+9+5CZ&zqY4``k~Qv$x?391)OlLJ^L^wmq6^GQ}6q)*5s;dB4sSB zRO8B}AjrINwecm`kuW-!X!n+Y^iMW<^wR=ELb?h;`-X4L(|votllbd4mSG6_^Or@; zE##I(yWTKC=ze5;Ka46y;;Jra3EYJVEkq5v^$eGME!g3$W2Aa?ffSfMhw2_3vzbhu zI2G`4MHcH7_iei_MwW8n5Oyi|#LXsks}(Wgmf&1`Yjjv{JQdY|uK*fS^4$#qnhB>O zwVvl)&xCQNpPYxY?cghAt7bhE$>##)Y<3Br(M@qbOCI$2;wSo#M5p6Td#p|g%&A7W z&)8CleJfij9~l|3uJlLrD50OX0slZ85LV)jrE+{kyS~ z1+zT$U*El_MiTu&U3DeK6Bz>C2aaLFTJW4y2s^y#PJi3<3m}g`34ZT?o|#KIxKi-8 zZrj(+cJ44|lrw62GA}|7XV~PPONk1~nYtvIE-moS5_rfg-d&+l3&B@0qR&H^qEEz( zU$jQ08CZ`7T$8EvE=$A5r%M~m4WNSh`h~g{rhkfPkHZbIScQ-G_g}1#)@`7 z)L^_kd9!^EI8(L1I@5l@8NfufmG;rw3xzAqTaWiL%`*j5Q^1dsiZhG-N z15NNAc_$)Bg=sSK+H?35xGqYjx+3tVu_{0F-50gBYBtA>r)!&BKAh(emD zVAm-4eFfjAXV067#}~8(&Gid2-54;~TfU(9kKDYj%7?-X6Mycers9l88`yHMKZ~5e z(sM~MHJ{gno8qbw+zCOH@WC;OvFt$uBN7^wv9N_t;6)>3;VgG2$};Bzmj^Zp)7(wYu<);rYFBtHU{v_58!ayT zd=Urdk4aHf_Y-|euL*eYG+OkU_eK{7c8RXO{F(HYZOi!pW~9rrRml-7b};f>vZhUYSCRx5 zr3WFim{-Yn?dFJ8xc&D2G#Q!Qz4@%e^BzfNy>r4_&u;jQl#KK&d@y`qc-!eLrjjUU zdw2>o6fvvc#}j<>18HGia^+*AJb&SipzyJK;Y(gCN2MdX(;U1)6i}=~FgQ5;W;W18 zEE7^92h6y>cJup6K2<5|aK5U=?f>XwZ^MGQ;klMyUbtdJSH%9LnUKg4O7 zn}Z&lq-G00_2()0vlb#N9z9Vg7g>UkehBZI3ke`b*PKEayK1hMg>qEMEl#AQ~ou?;$m2NIr-3&er^q@MDFKW?xplN2@@N)fKvy_=8)jsnF2JW}oeC zYeA(nutnFxx|y&+YK6x-6wb)1u4umdfpRuONRbymHFx^2+7*U!$0JXKk5&9n=Q!+{ z#4XMJJPkAeTR*~nL=jseO;#Hr22R#tXJ2GTaQrvHFo=+&-R@(g-Oc7>F-h%>M25Ds z#?I#fF9D6Q+vOED(o36)%ei|Novd$T>;hq<7;=Mo^W>4AW8+Z!g?(?M;Z~pic>;4c z$l2JLxE!D;ub0tKwnH}Gi=LB z3OKcF)3DG|9WHldz|B`iO2JI>yL1Vbg0IXO4k0f>QmdPlP`njt=2BE+RHq>t#NQ?~ zCki1ROm_`#3#OJKKa(-XZo%=wSx}H)u|}72z;rfynnU&pv7VN?l;f213ztm%5ksw} zUlxL;XD3+{#XpHiXb@b^ir{pq&TmRQR!Kgo-BpYjlKZ*^8z~x_iD70}h_jDHDQahm zg8iB{jQLo#8-!Ha8 zMz9WV+C9#M^Gf#j>87rO*V&pABQHa3h==mHvT~jhq`B~I+J76(l(Xw7)y=+kBR`0I z?gQ!|N@W=E2^dpsXgNkfzDC!$;WBPZk#Y^mz%D`Sj5o*0tW%y^ z&S#A4i5j~!MvfYZ1C9$$K$-`R8%`)x6ihMHi27E>Y+@&+hlTdGTgn=4Z#G!ZfifrE zIajB8q*F{qyfm)mZMXFqj!M`=;l%?eMF8_#0B7}dmAMd=3Q#?f!IO|G4ZhUNCeaXH zOWRMvmg>>x4`fizv;zt6C@gxls3;pwDhC3}9kHF#54OgvN4>jlnS*8oOo4oBbgIDK zn*^)_zkt?vP8}L(LZZQuNjVZ=z2=^V&%-x`2zHc3nsR{_lVj zvH4D2BWqG?`|CU1jBIuQu}1c;9g5J&-?E;uuZP4U;Uwr!#UZ>5a+h>IVerKif_75D za)JleaC&wcq~kK3Thd!Qq0ZNI>oV}1TZ|lVq8-1BBbJG@_R=fvmLw`JsP*Dz=rviT zJF{x7yjv#n=B!CiazWb_!ZpiBvEEs-wTmBj-zt<;trSW~IDQcAb@(5$lydwGUe)cI zv3$^xv3F~(# zwqz`@;cNI&Ktxy7OrM=J|2ZuNRr*OBpk?s7!>#pI&rHp%*!8jqX+b3=>}2)uasVD{ z1c zx)HoeQadwjH|;}hGIujql#!7TB4UDuR&s)tZ2autU_>NRe2yW!JsLYL@s=NIAhR!8 zJv~P1n0v=R={!GDJq^aW{PJ+eVjK9xahVQ*_!7pF`RlY1Hm5D~<4jxM1S$FVuLjz+ zRy+M=c35x|R#j*t5qGU+t|2jZi{1PEi+C~bQj42=f{FcZ@W}W=Nb;o^v;{470-Ujg zz6uptk_S+AMkfi=GSYC+2w@z?0qD~jDf3b66H@K#4+@DffB`QBe= z-yI<*{gA0OtK+bTupYPL*ZWqAL)`CmtfgFbJ^~(sl7+yfr6ql-^edD>f=akgsBi>< z3V%dVWytYRIZ=d;@#+P~hs#|bw=iA|*nPAQmzxOcGfG4GT@?#qxo&+mY%#VI$S|6K)Fbe6LY56{W9<3^SPkz6}e-8pfRD zMRvk%{~l&idLYx}^a2f#VXP@mR@9qAG`3mdP-OI5e)FnbO}v93#X(x(fn&Mo_S+Eb z8Z6OUtQgC)pvVTcXD&KtOOCoH_vXI1|N1DCRr3WPqbi6Wzz6QG)o&XK!F-CJyDi{)b3oB#Gxi0-=I4PQ%qM&zh$h?>I?uWdrygr=f1{3-QE`H75^#rZH=q9f{Fl`JoKf?U6D62zmSpFM7l2yVy{o=lFJ zug$akYVpjSJa!?&2!^?#nN7b+aGepE4N=DA%$Sq=k*aP*D?lSbBR?Pb{8xhzwSi&v zv@_s^pX-4o6<(^KEf#(E^H_3itz&b*ludKHS5o#T1~%w>^{$oX57kMacq5XYO?N9c zCdm?r64&u*_>!KHsONRxpPq?9CgDAk_bb=An=Pl%@MC2|kNKvDneenN(8=jc{AP{J zAuD-$s>w*2G^ZfMh4sv>X)uvOfcxg zV}23IfJ{t8{MgyhIR-?^b}>ZJ)uA5_8oj3uJ!RNg}b{xS4yQ)irYUw3;o*_Znu*!6pb78Pz$2LKqpT^(Fgv ze=`4mP@xK>ItkQtA;qdBC?a_QeGjzUxNmD;6-_%pE=g^==s}!oi{-!j(+_D~`c$lO z=WOR1%moudU``6cen9C|h7P}!aZzepki&J@8Blhx*X{8JswC^35Vnc(lzCD5wq)#( z2#IyfzD8GwZedSPAY+*K#8Q!`#SNf4M=WKcTo+A*5?-sbW9HmhhX^8gPeulo!y1iP zPR*bp2^N=1j9I$`lRdj`r3Z1TC1ZW^Jmvq;$dh?vwp*4_+SI-nKq$IE597wIbg*rJ zGg+9aWf0!yD8#@$jzxK?(H^3Cs6M9dX*XfdLG^Yh4}y+uPTluAeGY_vOu$kgUvl(Z zO1sa0Cel9>ViZ173bH9!?EJ%J3rsw-xj$XC86HcpSxZy!@>dn-gU}<8^0e=zBsPxzG_53Qyd; zxqm59#;s=$;Np9PN6<1ghUAdvcBnCA%Q>{m=6%yv4Z{LjSzni4QK#J;t#)7Z{T;g8 zdZYG0@Aj9ChF_D*y6t}geP6@SMBxy&3zrPIBtL+$gbn3_6NdW;Mv^Yf;h@>YogLLf z?5W@yn`iK_&-96a=Z}{+=c|7nNJj2Si=b1v+HtQxQj8mly!yl{_=OSc(XWJz_*%Mj zh&GLGBo)i(ZEv9bHk1tb9e=yWy+5XfoXa&HwD&tbdMS2Fe_#HyWuRH>rCguUw+Od= z{Y;{qJH%>h9Qrco4nu!OrT<{?BxCUr_O}n=E;YI%q0#cBGripA%~0@!9640>dU>ta+W} zf`{`WQ%kQPz&XZP+z;|}0PRT2RyGrnMl>Wuyw7?2p-|qY~4If8&nL+_HawSd7+JxrK?y+_M04W%yFmc23oClvtx_Y7C zKQU16L(fhuOYPWtZqsc-P;ZEME=Sy&ny-wlso6lyo6qs@YOiv{ZuebQosH+ktA&J~ zail$kRCdnphjRf7TBlFeJfFY>Z~dh*%1C=zOILh&D(0@{R*TP`#`2`3@~7*a-Jk6M zQtVb>$Rhh~W2mhbr)^ow6yk7HW}x@TWlB!_y1UxWOnVRg8grq8A7BD)NYon+))UUB z*#Apa9i21(xcM~(MuO}^jm;Qs8kIQn6pn-h6t4pnzO5Fn+BtniO3bKC%dTOrB6kCq5En$tN|$uC@_D3Q)IY)V5RNq_1-*jzBgvc_ zv=?o;(vB7UN}UR;oQ35zF7O8LA~*~x+WzpI$&5q0^&D;eWI#FOxx;>2#gY&_KV zM0_v~5}q97i_!nraf(N?yVG z{J4Aa?c8a@LbzGNo?s-KEpvgB)CvRY@VJnIn98VdX}^pbS2no8^DFi7^a*nE`a9C>S55g$mQ&UJoR`Z`}Rzma_fHwVEsSJS-_~IYmz0 zLKi-^X9$rXo$+tW<*jD+NJyD$#b+pi__Nsu-}ie_;+2P7uL5YY%% zQht*McLDw*4$TQ*&VA=h-(GiYF$dn$J!^9&EdWD{5@{aeDNy#_>WWVcRc4G2FCXjv z>w9gxb>>1ApSHgq&OVBLeCHlMuz{0%7MtTxnYNQ3a^cP12+a6PxFvSl`L#)ZsKRd}Z^*uXo zB1qIx9+R=FzKxR|_g!SWjyF&9M5aD{HI1F+KYyKHzR1!(dlw^`m%poU`)W)y(`WHG zjyZh$b`4eX`0NEIFXfx5lp2!Um^1Z#(tg_g)(JSC06MjP z%{u)|;fFEA*k(^2bf+6+4cXxklsXuJv#Q6zqNc{cmbbNlnw%Q)20FH{IC43!cYKvs z=l)VD({nXOW&`!a^zwu;xlu3{M;~zZ^9fm0Edwk`Xo$9OHn19Z{bn`ln9D(+F$#M} znr||42l3hbuF?RGVg#(*%aGk|v(&V}o8;`zvSbek0dH=dk|2R(mwB}I1igj}d_n{B6&+#}wR*^2OC2*Wz|AE7}Ha<^L zY`~Kn86%cVD31Sbk0WSxk{qhvGEIC|pM_RZ^Xp!J&up$ztZkaI+Ac?JcIEkl&+Gp= zcumO7=ftE=gWCU58g{xxh)!MiJ2{FzOI(MxnA#O<fLc{SSe{nVHAnl?*zj1t#atJwm^8 zhJ227{g;}?TSiK!^{rhc-Jl2WAX~_7r9^qG&u+53Fa9)FotcYRa>5>T%mm+UfqgDZ zz0B=L%E^lnk^^e;yFXQ!FM$;o43@F$zn)~#dgP^epO>fNgHCX51%Dc54gM(2ysfDb zn_plX*6#>mSi`FH>`_aMmF7IKBsbxdK*Jg)60#B;APx%#l}!TtY2k;^$-2;Z|7-oKREp=CgwYltiM8Ss}!*rJx+DEAIhTPVD9O9Zx@vIpHJ%BeFS|yipT?` zkHV3eu2)1FtR`>`-@SXl6!7eMJQ530LI?hD&v*MVj0Fv-B8i4)9F>-X8@=72=;TsR zldcJ0`?|l@VFaT^+xP^VWzA0zfvzb<=jk;|ccSQ<@@M0>;(kI>NT!kM@Ukhw9qz0? zGjEln=7w?s6;GGN#>?h=$m0tY#HDu6t_E;CFOh>u;$hAd#y%x1@izMXW1f?fr~Rqu z9K@r?8tikjKUH97GfPmMhj~9`3R##-cHt{w+oA{WWt#HOir&O8scR8EPTbuts*wpj zjbWOg%VwJRRuhCaM#*(2%q6zvW#W6O@}Qh-S)=`+Lh#)<9EOh?nj2gJ)FpyjoO`b1 zat|C#UvlIO3o}2oV39p@Z~bCvQ&l|?FR!N&Tj>az=XhmVlSu7wgID>K%MZ8 zvl1?#BP{S{SxDVHQP({mtO3J)@%py;-b+y$a=?fZUzT>EiB>sT%$J zqQqNjK97F&SbjW`$^lq}oOdvJIxS(WAl<@qApe&oB+4WaXT-6b5hxmm`$2redozF; z3hEq8UqlAd>Y_4A`aTyjS%!9P<3Z(EFJ%zOYv(~+c31f7<;^;mm<#8TDxH&0& z=Y87a2&8FpvOR;Wt&Mfk5?o{dNL71>m7J#b687B?cHQaNHN5EJ>w9g@Ec-{F%E7Lm z^?EMQpYl47)tCCZ{qE25)Jzq`@uW)?AP6|?d74;lwjInWL+@K=TsO`1u!>U>t}K@k z{&|OdlEiDH1SQG6q+m(By2N5N^Q%!NLrmsRCYo2TMRoZ75p$mW^iy z+dZa=a!dOA*Xn_bL6#v0}Ue!(uD`wp*Bzf0ekKQFoGh5p*Ha zRiTof6))E;PQ$zP)yC7?cI&}0t4Yt(TS4Kf67|M?x(5+0WpVeW>MjZ^C;xBzB#y2d zQoWtXTwnuh>+79%({|+?Yv5qOpaP{uf755ukN_}bh^WtuZ-V;b1ovw{?r$`!1%cDt zjmdNtkU-xRMlP6+>M5GfdK;xA=qWSkC=$_!oDtLmO_29a+13gfn?CM3{;Vuwk6U3# zcv+k8kOzLOHI$7lKr_PmiRpXSQr6{mxp&`9zRgAtV3{4mtmRY@tLnomXi2*k~M)A%-AXm&-R(`Nz8N0X0ja*kj35c|mV~f}Hr!=3?KdzCSWH33ztS zZgBVyH!2R2iP#9+Shgk0uToGW@IcujI~=5dDHql0 zPiI1Z?{iqJm{1}{OznD;@5~016&(AuTjOy|hWFj^(z1BYk~+CxY!Q)3qx9>X3|4uc zmKm(?BP^9)lSes3$@DoTVu&>|{RrYQ4U~AEPnt#+zl7xpg7_N}6<9w4;D%8V{9aUB zh-&)}H34yL5#;v*K{o;w3_qrxOVO?5TJ{~RBLb&a8f`#bD*Fpc>`k7Q*1;`Z@GqcW z`G5C}Zay^Ocu4JH=p&K{HN6(wFWUI)vwqNX6Dc*g;bF_V*$=S_kkQwl2zAw7nwYBZ z`K0qh*XmLElMSkn5MV(s?`X8^j%;^IIl`vj-vmHSK2+^Rdm8MzW{+Ib0~xE%KRS?j z@9cco@H?A|$21d5FIVba*)#S#YrU_j5T4rt+m%77eHmEm4kV;w1(-mtU_mip2Inl+ zUr!h6hQ&dG`s?(>#9?LY>)nZJJi}-(q!CC-XE*$K-R5jR5EY2vOAb%y8uo8c;?N3= z-{yg`--8Yq^ly+5QY(a(GBD(GSI3KmkQNFc@$AuW_`BMYIs8}2LY@}xDt zZGY8S>2!~({z=I}_0#{~y6a0FCLagGb-U-HoEE)2qY2<{X}+@1pEj_}{s|rM>j7;p zX;0cq%#z>L)_}8p!PM1t5dL$-)BZxIJJh0THkz-UH=E*GjxzPe7H9GZ+cD*DgMCem zoP|w!}P9F11D!0QjOB&pt}#CV?tTZ(*_=s~z-kfO<0*2~^6 ziU=t~UcZEe&?eDj(G}hSZz?di_5hX3ua`61o@vDER6&@o1`xb9(sTk_JMvp z<3V#{oUksgyM(5UH~;%i46_E;;HwYCw{3r&n+GlinK+D`H$efPy&13ZQQ zG~ixdK%;Pfo9x~H@~@hX8YiCX=@s(OU&swQtwzUZ%L+Hc3`_YC$kl(%9k#lsBJj_u z`RH{84|Augd0`}=qi?;?Uj9iGD}XRBI5ILnjQER)et>GaNjlfc*Epm5L9D>J-?ya- zaczygZU*Zs$F4SaIi5Bx{up14MR5c~+MF8q+1=T@S@|K!67*y_2EVWeUkO71n(Tf# zMMGF+LACgNF6FD`!RzafVov}e0-~7$ibBt;(Z<9XuVw%k2?sp_A8;s|x4cYvGZwp4 z(hA~&RvPx;Tec>Naj+nLxLXU5RU&`tS3AXL5pmlz_jsE7@9)p`@86R6${@VztEcAr zgM;sd&%Y~AB?-pr1ut<1hV6l1bK*!kVv>&WxPjT^SaW0(c42JsgCsZqJ!9{G)_kw5 zCs(+GGv0|>BcdPUCE^_#B3e!C5Cpk#6NIEY+Xbi`)g~9g&^~yeqf8NgjAg8@p<&sS zoD_xZdSh!PKdW~xJWJtLs#lh6TZj7B(s=l-*Ybe2Forx zSE_mTJZ&qEm#$Js#hRj0sB03SQsy?T*KGU1?p_5ca+>2zjO=xK!PE*WQ)t;c$J_TP zftK9N;D+o)*vkH?Lp5Ku@EQ&e7FPK9oGT`0g2_(Xh7qK^!D!KK)t`dCbl6emoUVY& zLFBUqN}r&I?#)$tDT1N&#D=6*G^J?r)T%ZwRw@ff$%FahAmx5}>T`*P)JqawBLCrk zohnowX|@Fe#sT9dy2f6`x+;O673l!OL{FZo8f0$!2<(or!~2N=Y&9I0!tKm#S%f7; z7VxwTOw)XhlOt(ip!`}&ml2LN4}Sz5e!Cs#{x`3irTRcVZI01ZB3~pH*4a1Zv!k8t zIUT#OaohK#-V2x?OaZe-apyz7vB9sQ=I4QoHuNzLt>mSt%kGS{q8{z zP=YZ%vx$4EM7NhVoT0}c%@`s4@>5?+fuX>6X-GEr%LrbNKFN{K?9qWa@7Snk-m)uE z{JmDS4C1tkIFw^G3)Iw1sYoK>;penmX5}~owg-O@#-y=CL6tCSHsQXsSRPd4i{Y4L z+d*~sHg!Z{HIpaAuxf{xXlN~*b~LB^Nw=sO^6}j5d75XIco7vnZ(0m)yVLz(^6g>s z`!DU_D``a4M=?C69jsw4^qBk8+a_tLcFdXA&WI&wCXTmYJ@EY{Jpr}Jr_!PZVy09PG*}6lL6?i5kfe|-K(;lueF)t6{eWlE72aGol)sBoWzI(GEo49fiH z0f@#DWC*}nM0zQ;ps1JO8Rz?yM-O$KembO;P3G5@C!uexc22j&%oio zrnrP5>^zF%eSU2>9UVT+cy@k%xysqX|9I>db}54_q^11jfq>#y`zNkLk1A#TX?)3V zKi?N8APCt)4h;Sg8D!mV&M}I^eo@S>oRuqVem_s9fvYeeb}|`Lmgv%L^z3?;1p5;u z%|UDx-(mX1_k#BZk|$s%sb~#rbC?`CT{IrR9085ks{XteN7fo@zQ>Ru#|p;z0D{FFSoPWx_@2?0o^C{-G!5a zGO2--T^@hKJc_PW*=NR;Md(C$WGX;;dn#&EX{f}nJ8_10I@!lc#!4pWv=OL$hcXFe@0&h^x`~66UW;rA*rSVWa%*Po zO~>t7Q86HbOXG+oMm`E`a0(J;?l{lG4eNk3FK;81VS7o$;_dCB*(dg)AOm(WZdNJr zv)SMHQJyq++DJEM(UAG;6p7n|8O->7S!n+)(=yVgmjRyvFaHU7g-R2mj;TSZ!Jhn8 zmBO)?xmf{W@Amcck6EhOVed)N6%S+<(HDx0?CZ>KXU!Pd&*`CV=a30V;#)CPRJdnZbuMm6f-Ra$M^H`Pe`&h-fCEu?JHRThIiW z9Xo|niX}3X6m7vo@p~BkmeCZ5?or#gsT@i}Gx-uP@8*waq5S-@QT*0pXgAOs#*m?Mvn4e33$nI!z*30C zxNzMBVJ!4-Izl;o$uDs2;QYOok5$=jKlM$BQxr;PFrFm)q{g!dYG6P4X2zAVN3nXaYV zL^pd~%=h-EQ~;Hd>lieZf6wJ*Ww*{_l^1`k@UNowM^2OU85g#_nj|k=KX$o!4U2J~ z%yelF@mZ*;wV$pTTiSf78rbEbDp)cbE5mVEpPZ7Px+UT1N^M~0=9>4bW=1K}I25#t zl7XF0FqW~gIqrz!tK~j^9H1GwKEb zbuP@KOo1kt8>+`!aiJ&U!P_s_GFMKOh+L!sUsHO2EY*pi)V2Fl`2hIcz}u+bVwXI> zf76!%0<{f!U=cK$?6Qc!JtrNsP#~+5qo>RGfg`Dfzj`)4>0#<&qGMsI|5ERBDW>1U z*u&hz+QZ(%*-Igl?NqhsFAYfAaHk7sUsMN7dsMfS;3jQMb(kW9ggduV(WF-8=N+;l z!CVFf$yCajuiHW@yb8RP#XA#qR4=Okki$J=KAy>tBn}vP?|Lq&oOj;60WL9x&4imBY$L2}tfzh-@$}eNGuz-WP5Y+&U;yc_ zKS@3_Z<+ngYQBW!?NScX+(GQ*OvvK5te7p#ia%4!jFhv!s<3-S5jX1Ih$k0t(z88p zl(_$w^aW-nS=^Y=O*r`;jL*-DR!kv5MlT`9-crU{ViQcoB`~~= zJju(%YbOt^{-Sy^&l|Z2*d9U5{@tTKEZH9a``68I2}5LALg9gODivufe~+49lRPD0 zBx_|(f>q#q(GjW$j(8;t%df119StvP-kR-DO--M!{V~pGnZ6XFmVT(VwBMdrXY+AP zVA|P(oHac?J-cghMip_A^Jf9u&;OD<)F#6EBVY4vr-}+E0q1XvMLFN|_0Ed$PX|$w zu2_aROi9ePtF}%3=0Rf)|4ItrNt}OGzC>3^v*f_>821saxpF|~2uv+~kh}U^zvIrh z6($`E%37jLiK!V#{zcA1tJ=0uj8mAo{iNc;%l;1M8;vs^C3PZQK`FqARkn86x6bGZ z)+eySalPepVlKs+?1zi+yZT_Esg4hL)(g861-MyK7uEJzJ)S4*ziy4Asj{upW*XB< z`#ZugrmI8GB2|{3^s||tfXZ3K#3F$`?RBLXV|N6&$YhjnFy9(ceQm7?zLAaW%#z+{ zbj(xhyQ0#nhQ01GN(k+=?jfu?4&c)!Q~iEADCAi0y!j78AINC4T$c?AjiO@ZoA5`q8c#h!HQ?>-)5qnR_G?>aza;b#EK}IQ+S|CB$~k8m2BM$cl3K$Up@C8o(~B zaGgb3`~H~p{)0^~UDd)^NM^9wOtti{LJq`p8$j+qnVmyl3%6?V&SI4pR@@ScwF=o^Jz>=sjK+n5ZETlw%ALPl`%H7m59rm!;>kI1YXo(-u z$|3FgF#S~AQ7Uv^dI^n|%3!MIYvCp1$Q0DW7!H7lxN-bx<$`y4xFfJW;Atj5-3LwF z=i91f>=EJNR+a8s$?>Nf@kf`D^_{jsguRbLmu(#%W1eb=8Grj+?Eh>(=@Jkn+iV0v)qI9b?UK?FpsOmphzHnI&oLf!TZTkLr#0W+dmlmZK-+>y;=9`?!! zd{=0j)rJ?5fW~!2(Ssy3gVhW7I#>d$JZ;9#^!k0GDUB;KDA^ykW=H2qUAvXcte-e8 zP=M5IM!1>6t4L)*WNP<7#y1d)Ljct<{6P3;iaa_gLZ$pkw8ZDyHd87hOGusb$d0z8fcm`I1$yZN#!6|<)G-YOazijW(VOu}hImmC1bgA1l zxEEKWE8OrC$OcH3|_O!@AL*%{mnBJ5XH+`^~_So|I@sZeo^;i7yUwEt|D6(_*byJWLrwfflu)R}ys|OECQZ$ZgoVoqPVk%Urr(8_CEor$ zGJyjLvPj@AkN}_($mB!Uj}n|tJA53_p0xdRnO8=I5rI1eJaGX@`H;6y@uV%p?IPEA znnhHIPGxckz!P~9cbR!JrT@zqbsKQonsYBAZ?Q+P4||DiZH2UI26^bQ`~D3o`hxjhiqbv0 zV`{`yOsuTed}jE1x`vjLgxQnT{A-y4yu6me<*S#OX44oucRdh8CBSer)@w28Q2+O9xAAEH-RkAq)TMfRBjrC)Ae%X-&yYC|+d@RJGhjH7w=lp4jWj=nF9EW`NH~`4Yyi zA-HpbbnUw5(PuNQBvg8EOxmWvijk1mWuCFap11gxyU`p>s1Th5&sop?us)ZC*Wma8 z(3~ZfOUAF?L@vlJD7j6-F3D{4b>J{Sj52m+cYNz-UBuqN4 zt{_ZuKUOiq*gmC??88B93s93Y z3>M$PK;mR%y6A(l5xCH{a&te36wO-#eeia06L11Jx9r}8x+>Y!toNK1Mm$E^oD5a2 z)?xNr7%M^8#>aCMRLsknwz!Q!tjPcGg(-xL53J6$-VxpD)r-zb_3`-Ic7}_k7P5}m z(%TE4HQ&f{z_XTZshysdKzRn503ik^<)RXeR*z@i@5$j%D3Q>DF$p;{gP=YO}{Anw+ znE#&(8r9$(!JRIa`kn=RohuVodd5cvoH%~|)&VUuHc{#`zD{T8Eqb|}gCGB{sef*CX_ z@ukxfjt7Y31vEc8jNuZXIeq+p0`*_GLTG@nqcO1;9+tg&s6SE7yNDv;C@NT%b@cjZ;mMI# zwZKnT5AG+LCUDvwMFs%!s|&eB36Zo;emN6$Ho_^$>o=A;fDNEc=Koye;+RYYWCG54 z%m`PMZ!bXUSg{&&+XD2_bM{lTg~i2xC+alO0j9^rpVdND{GJZo6cpp5>N zFB@tKvhHgc6C1{#^8X15I2g{|*Oq36ZbOSDMo)3d!`@ak9iXj^Vf4}K{u}72w(pl* zPlE^t3D;z9diG~lKSk)}##8{@tnP0!JN%pi5yQVMk3K+6p?MZ~%Bso`w67FEl*$;y zHvoAkD2?K+dnOeSs)+>>k{1=u$3}Bs+K65x@2Xa3JzDMf4_Er1OC}6{BzDC6+e}FZ z$Jzpy*ix6bjMfHvdkn9`ycCSnT<)~*IQt;xeS!3855>U^zGdbaPO)f|IOligbMm7b zUq!bYxALNfxkZZ8l&&1HnISdh4-F|TES1S(?fBktS-(iH)Q0Y>KMD-=8SXV7{m+p6 z_bfXLJL(yGGt_9>QvEH5x3fYwV{A+=j1f?%s$^j3+Fz&L2xOC0^g(TQjwK zW+!Mj4So&LFj3>|tf~^sN=;>PA`ajk26R7j95lB5!X!g$9Uki$zSMB?FR}gvj<~qj zH!%2?vVfz;dbIZcdjU~}@UbWO2Tp_R7b8K9KP=|PB9XF;A0%DZ*K4?J1C8?&OH*1} z(6gG;5O%g(=?4$U@Ml@WEGBo7Zjz#qzmRZQSgC#U?E?Ck4rWyR1nr}g{rW|jUa{XZ zaj7ZK(#Xlr%se4m`A@^#yjbWD5wTJi|F8eoc7QhD+Xulr!qA){ma@}tr&LMY(pPNa ze@UqqMyFaL8B{P!F- z1N&_v=HW~1dkN@wR_y?Jmz!<5q%Etqi=uSx|Hal@u*DU0+q#Xr2X}Y(;Ogy0(7 zCAhmw<4w>Q?%FssE(z|?KycT<>3n;i``o?nebygXQnhMU%`x8b>4!c@{TA%5BEcbX zV8qh{0%xEr(T{|{gtwlD_(Q*vqF0fjYu4!RziVn~Xha41^Zwb6_#m$&Hu8uQNXx^s`7IsbheHtcd@a&2@6BIPAJ{H5JTGC$q~+Q*jCPH z(Vj(&IRi|td#oq?|7ldPKn_n-B;RU8F$-P|4Xk=#{u~Y`)wtI?KQEw#(ztWt z?A%39%GZD-T{s8#ZR&p+HMb<(?A4QHF&Isez%2BT)b*}yx4wdu#+ zsJojtmg}3ZhifEkoA+oYwJkxY(D-_&{2WUxNyS0~(9tmhHiJ?t7Lzf+wBLAzD>$ka z5vgecOihDvTDYANSSs^LZRpt7<5}UURyGOJ%f@+#bzWu&+hDY}#Ho8K2|9eE> z?gNC8=}zfcu@jp|;uZ4q$QaRtj;^UjkU;Tfxh@#Dn=0z(u#hAjFv@(wgg}DqLtFD` z>QMy;i*>N2Ww`Y|6zG8T)^T4`)tFaFZ~c=1RD&d`Rbdq=UI0e!^VCPq$9NisZFc_O z*JkWi=(hM^P&|UwPje0Lrn<|jpFfFDb!+akij0gmxAV!d_n>>9(>IN#$%%8QA>6o* z-DDdY<}T=aNmp4+a12%#mCRgR>x(($dW7l-sq>LhrKx{D9V3(R{aAok@Wj+A1$?(r zdF=b5GBs33QTsnGl6DI1g38vE`*c4ZW6J<9hS~KQGy)<+U+{J*zx740o!dR9Gmj^& z1y(t;AS0F`KaXgp*+`4!0b8`(wcw(#-j`D_tg*azuZ2&Q@{~vcyOCho?3TjOe;d!tAb=lpISU@ba*{YCX zP5?|Xe0wXjqsc2l-=B~DoL{eZcpaup9 zgbGIIF=-gKx3^o|Y}t>%Sz}384ria9a;YFpPK+b2V&fAg&X-CiN6z)72%pL2R+Ns@C`#h5;bKO3{QR>(~koK%E8NjP)StgMQKTFEdYN6UyBdoUWh)LBHe7r$Cw8ATITt}4Lu2JI zDIq2=Y^YN*rzi2DJB>%6k!ZN-%ebAEvtkejY}5Ka9S`UOoV`}*mVp`Q7=>8CRlt)= z*2I5*XP$Xc5MPFp1oa=A=Dmjhj5}@rclx`n1O^JujRiB;sN-=)A4+&}zeKE0cF^SZkUf7=?0)vpDno*fT02Vi-r?b)%p)H&YNZ7BC+nhy zzRqC>xaG6dOf)DRL@Es|4zYF;_)l>fR+@~E+547poIb>5WUn+it-0^t&o-t2f$jUe z&6n+7LyW!&-nfs2SiA_ylN8#&duvXH-bN&Air|I{_gx!aVWgZ@<@KKpxKR=BPi&*o z3kq_M%*ZP#6}q$U^e(HvX9|^ST!`y$M1X52FQY`8+y4Ed>^ymBwf4Vv4apwZ+N83+ zO$?fUJs<`)dyh9qdl$ph!gIaJ4qI1`nI%(P9 zN+9OWj*mxwcwFe=5?=GlI0PS99>a9bIS`1d-p!}u0N-IsU!a{59k)~Azr9ehIP6yG zsS=TZ4cwsqbSRNsOh8_v{i{Xt0o?&#T0RydW4RxG?fLwXv$sz(;<$H2Lv>y~xLlp7 zY0L1Lv`j`^qTW74SuuAa3NTJu(bm?s@Ij4(kQTxF#)(fzsQ$k?{vZnNFx3bC=@gI1 zsVow$oj+Y$fz}^=&ek~-h&aIFsF7GBzeok?t{<2?MP`1 zLi}r@v&gPLM_CY@4MR}wk$P#x($XQoGekFixcH#*QZ$F$X@~GpUVV z^*lfqpGP9EpddW`AyJJ~k4it8&}PpbBKxOzK9rnFf3f8EuV1+WhX(0o>Vm|#DX>2Z zSZ5FpT`vnRz$r==)ZI;V7om9TW+S0lXU{w{Qr@2TX>@WjJN%1fLzU7_n)}uvmVsiu z7oq;g!R+`vZ=bZi-OmMX_JZ{tadntS6-@{p*vJl12rHcbEX-;nATHq!a6sVdk@JoM`yC*11y+F`l@@ambF=T*bbbKUpwNqAQ3XF_T zOImS=;~|JG;-4MU<7R2p)VX!%PgQ+0<0q~gJ=?ff;5o_Lyhp&SDWHSHKsjLr3KYal zLHPhZZ@pdp&vFcs!p41tk!MkaUDuCQRO6lLHM4?Xq|!L`W60-BGXaakKgaMqIDky% zIBF?zwP*?r=_M1mRu?$Wes(XsQGrM;Ss9g~E7dB!oiIv-wMmiXU|)fiZ{C&Pga7!Ylafx8Aod``ASk?t51(VHg)LvAx% zh<>fHOS2%2-ukK`bumEjnc9)6OW5tp-Q%3%;nlfFz@#06Fyx2Ba_N69Mjn8BZeVe- zYp8z+pKAZ;+WKdG?`HhSUo+LysH%&`Qw&B*iHvN9NgSB!rc|6e1Rv+O}ct|R@S0( zw!z~DP5NY}S6DskuoBs6wRJ{knFf%>I!xi2cvDqAUa$0TYFCK>^fG7~p{s3ty_}fJ zOTVzjRsF{V{59T8u1O(GS1^OSDTwF<64dR_>{B!}sNXvZhqkG>z*?Q|N54aWMS$16 zxC2b1=k;H8Oo(g|==G+Wdwr^S{a@idPldki^D@|#{ySK}B*13b+~IP{Z+6Kp?C?>B zp;w>kh8QQ6^a$;zKew0vjQD;OG32v~qTz3+l;C96qDs_r5zp}q(rNu=6 z=#SphqI+~q-Tr^=L4w?+_vm)j0a7lhavAsrvGwss?taJb_~`dsuG!&$t<#($UG;!^ zW*>j?PikETMwB+JmJO*mV_vXW9QT7~T>{>4a~X#axiB174F;D;Z(re1QQ8ht??{E@ zEqx6m)bSX6cXP{8*nBtV5!np<_+}(u=to8rhN%!}6@{lLlvE5tvW$!z06IB19@6YT zNk{*wNQdNPfD-AH!%zlQvGZWfJ}y(Sm8ll+zELLMB_OVzA~9e~SoeGPa$nh_9OR4@ zTCTKRtK104yK~*RX7t|;Im=oTYoXTF)(lmrYh^?|#mKx86&`Gb>Cg5$HvYQQSVrF6 zDLDW?YU@eEJLrnAyuu7*tXI$pevtU8S#8A=jM)i6A~vDHk+@E6BgBUIp02Xyn0Umk z1|0qD3#5x%e*9k3z1(VFM*eZ=*P~)|(?T`^N*|o@_@6WBz7?MS90ZH{aLED6Gdf(e zx_RZfGRrVB&zD%6B$6TCP{)^6;>d>nB?3!$PuPuIPMb5oR3I)*sx z^AS2x$+p0JD~N0jYVwdgJGB~TBLOKJnJ1;^!2w%#`+ptx1i6DB+SflkH;dnwe%^en zQNahbfh0h*K9Ec)lhz2Uz-jsuzz?+zGcGNu8=TzN#}Ak5kQ?2FS&Kez#7#L^bjF_# z%jI=VdY_K+8EH!mO@lqr#M=Z^aWC0Wd9?h6j>bjnQSu&gUN7vHnr-CFqC8;%d9tF9 zy(rWpc2ZA94QROhZClagyIBUH8?)AFimx{nwgE7=`Q_iU5VQ4wBTl_+gtP`sg0_~( zA^L-V<_?;FnA*cVC=_D$`e;yU5ppp|{JH6fxxg)aHSlUecH(VZeXbt(`u-ATCBx|I znO|5KgRxZ|cbYF?Rf`arA2w5kRxy{<%SFmwOV8h*agMLBv!;HylMvqQA)h~!DTfDt z?PnX^qft<3Tm#PgeF{(UCf-$%o<)zz$C{^>T0=28+&RKT7xob&!u)oBR&X#`bCkPH zWFqCG`Rj=Isi_Nm3qb*ju^Tmo9xql+{5g@nk-=2@1=J%TkVK889jz+8+}R&H0=FWd zi>P=z%QF6&Y8E+0Kl}1gZ$!dqzK2$fI2yVVaH7}C43=IDe6RCH+ z99KC5HM-3mcZYpM2nYKd$A-Q4ZPQs9A^ZE0QRZR}0dG=K4{KMp%A$a8ZWn*GLLT)z zQ()q1=s0g4L-^T0w_k8<4YeB1)4so}SDJ?lu8@$d>uy8yi^}McIKCHiX z7{#j%=Q~=R+3hAZ)D~=Y@|3T1`V@-a&2NC>7X@#{=0GB+z zho_lke)hgd+qg6mxR}anpA)Q!iANHqdT8PtNcn=*+oZv0nMb&NlWalU;3tn(i^TrC zy!36n2xNwL)j=?;qyQk>Bg-e{*#V`mqE`V({LxE?n`fU+ZBkd4N=c0ZbGo9C>|}2& zhx|lXrme0`fKS348AYJ@AACM>#rPALoQ!OoW_!u5SkQxP(IbZhS z?WG7nbSc8jvC4M7e&D&H#7SthPRlJe2=@{7ntak!+hO$O(fvLts%Q|>g?5lm4i zFTS{08YC))i2{tt{~=O}9V{IPC9;ULPS<@>Zkd$;dg-%tcG0tVn#!xc#~FO&uuZok zvKlB#kclHiMd{Zi|LtK;jm^Tc&m_a$bKh8IOHBILhq+m;0uw}X4sYmLXJ=CTfeoPg zdKMP=@| zZ)*?`ANp;_v*DEg9>O~{hP&pcSSmUx_s>pm0(MioXI(}J4FsUZ{1C%t?pxw5d61^w zXzRTQPUeiv7wyal&PhTKEMR{oBbDs${>cd0oMrcA=rN=HSJ?^<^l{cqD@G*f-?l&T zf`7xd^JjN(qWZ#}=_-2$u+CuMey=f`E~oV{xrg-KDZxQg_RWT=~6Q>Ypa{kV!Q4; z?nx23xob8;3cJK~;&5-1*hPrhk5_>jE;o~^Tc=S%x`TN{iPqz`*E;PD{ONy*wMUVI8++_Xrty3t9JG4zOMkGUju}g zMxE}}q}(0I<8VV3J+mj}ZntFSni|Os_9~*g0Po?b?Dd43s=gH?QgX*nC{!DPD}KSU=Pw(ET~3)N1N9BR-z;a)6pDgZ%Z1y-WHRClecs>+mt)q~#x81RE&uHHq#=+{DJGTD)-9b+@hr{#*=qGEtg` z;8gw$T{%Aq#)6i9YQOwLN|kE>t+iFHR^}XvdTle@9lDF~Fo{#LB{s?+iV;&24rpo^9j4|u_ri( zQeijenleOf@c$T0I{%!ug|l~q@{W%1MrKs)YJ>xIphUM?ZWJNk;F<#TV^i2PNh89i zPoX4b`5lLH*+&sV0F@>LucXxGM%r-|OD{S8J^EXeK*4-ZBabDg+1Y-|*cfHdxPQIw%f(b&Bu4)l}Em--@9Y0tIdl#3?!k zKb^FYFT@paGMwMYyjVU6I6wEv=Ae>_4XZzXMpqkINo`&g0*@n~>dP$(Q!AWYsY)U(QV zjdr2TtYsmgh#l1&J4qDrZPQK~z(mZ5uV{WdMZ42x_oOUQ(H zs|A}#Q>xEvDMt%4>@xK{w%Mtm==BJNF5fsjJ-#Jt0uC^T$v9#~b;`)(ze{4jpCEU- z`gx4hd5CN9X)O=*e2$ftkc3(TS`+iPlAx^l}XLmw%tpv*2B@paD#S9 zWamh{M$}*^J9}gZE4Myyf@o=Ol@@Cn^clYnjDn%~)gHt?s0kdq-{xUCfBSQZg%+fv zGE^b=>UW7o>N}3eh3XMi$Hwnaf0QQ98(w;sIpTSwV%y_V!0POImAW7 z5`#8&q5b^)wA=L+Ka-%qN}TJi5OHw2LB^jr{)N2kmw55)&i;o!xTg zU>f4UZQWdP=jb%gItbmKi~`aO%3j>nh2E7qos?e+QzYWhAGuX=udS3%<6o1QXM`Vn zUY~mM#ozVzJ$g#J!LSLVqWc9_e*IAeqN1XTrCK?043CnPSew9{dD1W;H&W0Vo? z%8UqZlsVBt85x!&Bch<;+30wbh^Av#^xkzflyx#;ph&DF8u>1g2uMmtF-0qkseAR- zr>d*$e`6njHjIo7(<2B?<7%nf$SSAhynQtPK@t~UAh4)cyhU?&@eZ(81LyRmhK^-e z`r~heOsT;14p703<%y&wr%33@&5nGqPsK=z7Qw^lA=Sg7Nm|D~_z9y4(aSkSrNEJx zW?W2j$6X}iDJHFItulwq5W^5AtzyAGX!&T=GzXXx86gVq)m((dDeACm%n@B>fqcu0 zFN`@vOqv{G1`w3VnAy*V_^?m^R>rahQ)$iSdtqWe~e z2$Ht?1)r6ZGaFOtnVLS(O07WvzMusG$qOY2xonuWniWAvztjh~r>t_RrZ~x4|0n4B zh2x#FP;LUtt36qJL!GJT7IPd^AmSck`#)^iE{t0uy`)w;eo{Jf5EoUAX;5wAAh7oj z3vyvM>A_M3wbbDHW6gX!tHw>r*>;KzS(8@z*MPn7gc)UD*G%+G#cWedje3H+3OpdJ ziusx~+-f;of);P?|I?=gabtsAc~#kC!)7rnhmUH$Ka6{&m}n=S11`+`QJVJ;xUt;M z*gt-D74ZNInpbD}TBq`V_;yF(M8#(lMse*9M?hA{EP~iokGQklc^t#y#M+3~{cdGP zf8Z;T6y@pAzK}!Ao=>}a%AVbta*q*S<`m}}RutUzBuC9_mr$Xn89n*x-WV-(99Nft z$Cz|v@N2q9nkQ3uORJZM$K>cljf|t)SU9ND45OrhB0;A_0+_z9z8B?)NV1Tb;t=^~ zz;PW1CuU6Yt1w3~pClb$NCly4vKDrHRg-o+Bc(<^Rg4shzE30k8)5Km{${RE@W;ir zgaq^tAM)LC9F6$u=JQU7lVWW-=9|;sS`Zfjvlk$uuHLYy~XDc9xcVmPkz|J9|22-brD+E*&M@)uCjr06*n#g!*X|aVFw)o zFFl8aQEylY(8{vtiZAkLipl&K1L#2FJv+%_iR^Mi%pr$!&#;12sG8ugY47FfET_qF zfy_4+@qFQ+PYjL%&h$6mk-=gmY#YLd= z*MegC5z$;`}Iz?0d#JhT2NSV5mc#zM{he#cXCh?3V<%Ho|9w>44F@ALcxhhD-hP@B3RH zR|kfpeD3ms{W`-Y1#1vUaWONKgM4;9K+P49#mvtR4N1||p|-7+7zro{wO5J=mwh;G zF?Vn*D{q$>u)yxOjdArz!Q(C74}0?s6}=M{$zI;zXGAE>vg>#}dw4~0^|{d)CrHgL zC@QLGRUo{YP_Mzm29lV2J_|2(xblsGaS10$bd@u}LMq8Ca9Ve6cM!#2Y30hNY$Fy< zPBAcDK=`-vyFF}#ajPJH#LCQa0md!OVnpbwNUZejzn%=UKUZ}b^f~*33-2A;Vt-tM zaMpaLe2r+8wsv*iCwOS( zKI2FqSAHUuVLFb<&}rCX#Vd|nNlX=FwyM8b4=1=L_!(zKb_ zk0U2Ve)Ajur8C6~=ZRAfs$D`IUMMW{AQI~F3Bs>k?0j%n1SCd=xSPqGfryH1W~nvX zf|KOkg1IJ4LxD$f@io)*R|PiTWpd3{5Ira^G7oS*Do^~jaO=a$^b8&29ejAQ zA=hiXFHq#PeW^G8xZM_KkqK;EW&Sp$BUEMIz3tvU(N837t*+GwsKfK*BMv9*f4MTa{%DDyA;U zjV+r9p`>a?I^FjOl=zWw-d&gCdC9rprv$U-FGg-Eb7XnZZwLubG8orbW{2`I_G z2?Rg;iC&HK@4+}Fu+wkiyhc)`D=ZwxDNYnR7|@y|%CLU`$6cqe^!^BGqXayky#tHW z)&RbXHXxdtDWRsO_OMB;f)#m>yMU%`0li%(R&q@dE?UEw*#2W09_}LZCiP+mdCxh) zB+>T@%yT{TY`wGO20cN9Bd8{HU9_4Ubh(<3W}(v`h8AY+l|2S2`&(C*Ju1GzJqGyG+3ys8%UNP4!sxlOJ8c3xRnpufCSQQ5+%?(nC> zv@8KhFeas2%0X{mfRqsZ!SN@47yK3Tsdd%6;5gPHay8q4=!*|(XFS)Kb5Z+iXkNLC zjA+vgp8<;m&nkuG)BDVGI54wq* z3#^|~;H+Ika6lA!2Wzcc2+}{{V*_8DQVLMST)N3W{kRC3RzX4?lIj`=L{nCNT}~wY z<9&{cpaD)yo0Rz5(F%W?&;6wqaOxSV;zGgBM>j}6VVLzmSM=BE`^epU5S8*%(cA-- z#xM1**qgHe36{I+0kq@mY6@l#2T@zB%|{zOJ#DIGnoBCD+sW|Fr~P4Zk6Zfzh4;p$ zoDozvFK1{Fv*C}6M?LVtMLN((>3O+6EoR{--zsQVkvQ$&`tg*H6~fsAvDcV`z{z|=exs3?ua zEW^fZx3t{mLBAf2F-_}oV{fILG%84S>M~ z`IPCIT9d!ldt%ro*C}E)a_MslqKclfi?A>X2E))Dnwd6@Klz!^!aAl7TSb6GVcslt z>LUsf5uaVeeJ7%1b77@tw6*QMRQkDC9eSUWJB#I`&QBrn#Y97k69{=4XYlh1 ztB?{d%e5VA2i#R~VL`lsg!iY9AQQL}g$9!9nY}lg{EoE=ho8!@K6DpTAEixAL)lU?GG0iTDVY~cgX?w)6H_4-T%%mE8D79q2Fsil&BYAg{)$3lPl)J_2cBNx^>rT= z-uLU7=#rV3iH?;raF$1wl ziiL4INpo{>>=*KBZZp0TSHbF`rQam-6i%~|B8zewF2Hb+;bUe^Iql#?kuzFNJ~zZ2 zlU1#PEy&ymEH~x}_uknrHs3_a%H;O#k^CJR(Mc3Jcy$-cA|D`V#`N zi&lKvKMEhBM=bV;{zAwkShK~9E7_7919NNnn?OyqC*8oGg8}RG#fEVn*R7SK#EExBW^qzh1%_nttZYe)0oXy7%JYoK2Z&^C< ze3vvA+vVs}l)B_w;*=++sSQx}APa7Jh)*7v9?(QLNJ+TRn{le-_t+$H`*5{U&LU)N zC{OeFD1hJ&bK!Lv^Kl*KWu|6Lo((ziRie|ct87!^H;n_n6{=iBG2I=TW_-?pb&WpS zTn}ze5x=sCFn^V1oLE7+EnNH71aBv=?MGTIe+ zd@10uNj#3$y6WUm7u>S?!c<_((dA+$cgaO{N;W|gjzvnp?c(P_%AqSQW_XB`>GH#W zm@EWTzhtL`gR*$1t3Q2x3E>O%01Q3}#XggVT4Fyk#G?ksFUCu&&V*D)T7E;f+9OIl z(Tfc{e@^w<9I>q((TDRcI~v9?3)jVrAPCb{2mSc`ppe#6l3t7`f)l5&hnb`oA+k?5 zp2xPTXf`hL+IN!?D+gLLDEPFQ$Zr%bIeGLL^ZYz`QFlGV1cH3$l5%3{Y z)`A-UVIkL2T2zKp>R#?0hP55vXJXksqd?p#1F@ccbID6GNv4KFTzJl2{q-xyhxD9C?Im6Upn}o;Eh|anoASSf#NO|WyHJ*qfy3GaQ#b_! zk8=gl{^uisisf-mFOefCISJ!z?qqJ3{J{K{vgSaqdcQ zw^L6mOgIWBWGq<@#_z~_BzA2(bLS_7P{QLObv2Ob}!rJJQ<)vj}7 zD5a#g8wV;+2g0B|#?8my(wJ6p0I5&*V$t>I1%+ZrURGA6^Zh?d>y(s=R-ajBuSUC@ z8V&vlI_f;6Z3acHE;PQpE{AH}7L(srK)PA+D5p+_x2Yb#Nx4Rh?bv8?+-i}9xF&9S zR|GxRQ__v`^5kMGe4!>}A25%6_VXiD|1fPvP{VRFv~(JJd05+IS3ox;)AJk>J0~bz zfHlrcr)lm8^|q5r@W@i*wnQ3eM-yRVW~*giKAA4 z)m0^OsG-1J@h+YgCH1Cx_nz-n*=dKfRU0(cJ6?w!9W z71*SnFJR0jbec&J57{d_++k?pF)K2wRffeNF_KHJVhexy-Z4*SDM8T3th0A`|TE1`v8%Q`-!rVBJPLGr~ z@WA?F+tjvlDt|X0ZI=KPD2LkCB}oN0HTlkfgf&^S%r0MnF6k$wppyCQfK7OK1*Z;a zO4(viT_r)73|S@Dn`A?5z$ON&km4fBj%c_-zq-P$PtWw@vCsD!!Z z6Itd2D>X)(Za#*DU7ZoZ@AQ4S43TV_*yr`p>C+bkjG<{r0^9&W@7E^)2m5sVnG^;F z$Q;|F3fxw=kBpoPPmuLhbk}afOGsdO!we7tbqwc2$;WzU3cnSJ#}ij$=}ei6kUQXYWrltVpfAd>()}xjoj`K27SaIRHO^o>2QYRLZq|Yy~?`RN2lT_RB zDdP8|91tAHCrS<}_7U}0{hByAmU{Bbdy%8+!%%OfzYm2E_TWLj`CD3EmE=M`)9#Rd zo6GUOcb7TBIVzsMrPhp0W5d-CnThP987k+)L{&%Gh3977-z#K(|J-0;J@=I4V&AY| zTN*mdifWEF2_%i7H(2%)wf_(9il>TVx@ebw4lpS!yM@U3tEPmo2LYN@(IEr4RWged z-6vYqZ=jaKnZzWL(Fl|s1^;cmb@&08JE%D$pGT*X-bT{Tt2IFIKARY;fI?6jwQ0xBcR2&gKK8vljoY*XT z#`D|_?U=%Yd&G>$|G@J_60I+SA2(!~i|_1R+Am@x8%wT}7D?mcpxGv3B|?-&tcR`M z2=wWLCsPQY<820Jhl&^VV_R?&>jq=%MXP|TE&Q)M!+pmB+Bdq?NjS zLT;lQ9NoFF&O-kCB(w3@da1os^-msD?NdOrHQ^f>Q}8;4%`VKq@kcgP6NgXIe*QlK zeM~Nt#wjXo<0_CV1|vizvu~_4~AP1_Xa|c_trcW zlG&NCWk@efY;e@ZKdX1te2(}~ilFt;9qh&7_jgZ)ZlOD=Rlm7p+RFWmog}gL*_gQE zhr5W?-;p>vGsDPy!1eA-nQR(27Oovr(JQ=5T92|F{y2&N(w)l*ee#vp{i~pJzdrpB zM$lt)Kz;kvm4g^XJCRheNa_sxe!LM;iJ1l?!-b&0v~YGB$Q7g=F($O zaHF?gHEl}9a7ObPWtz=I0wDrDvP6lrA_A`Yf$J3w?8H_|x`nSQO8~+L=Rvuds9d6u zdb9k0KCOG!`@mmPL_`ns&ZVN8zpQ4+I2mzyEIHEEg!lNvHY9%b=#Ht|oUc%F=-{Ah!YBKlE67aOgRi`biH6&$COzRpM>z+r zN;}-CUiw^FQ=Q7Pd43V<8OO`K$$(ZG(V{zJg-t)D)z3FjhOt3vF+%^D7XJb-m_)P- zP0Z3~U~D;>$zd>dF~;B2Ea2PmVx1b%WHC4WaPpwgjS&fhSMQS=UuUypqB#dw$+Yl&6^Y28oyfkuuitQ_Mf0?h<6?(r+pHNV!L&r+i^Y zdComfa4A5xbmJh$Tv4rpQ_SgAvTQ@U)Y$$J`ZdyF%cPbto0+_mwWlKsb*#5Z5B`1d z_#R1=>tB)8*DXUhb4b{pGGNre$BIw@ASTQ;U0ZEKr69|}{uP_-;+=pFT=^^$N@HR- zmHU~#{itiNeG3VhXTv!`3K~SkId(z1Cq5BhHQ}xSH(O{+O?%4w9#>`PWn5G+WI`#i z!M02X!CBssg_6liHS}pgPC1h{B*4vo2Xi$l^n(#rhaajIcIRk+t&>Lk?bOUfSSBnx zIG%yLWcarCB3L+)b!UeFZn5r($>qA1x6j&owUt-bI9gFLJPzGqfASN+)(H?fw1KR} z)+=3!SVHNF;nQo9)Ypum*S+`ywR;$W6bYz)sfO$DG{?^gg%-bTyX{$_keQ^TcP=C=2TA15u!0MJw3p{_AwXt9O!~{5C;_LI8zh6XcaB zvw=r0xX)8YlpMW$p&6)zJZxWlv`R`A@)b$S4qxbzydy-rnsJ?Z5)S@2mxo1;6k2iAyf^BY6^^;83X}c-5USB0U;?gzriqkYlOv zo3D>#KCY;b+TUQk%DlMwMW>xdKefw?xN%{+{*KODao|eDe^CTAH1FWy%+Vb|K7}h$ zX`P3%d#8{S*9+IAEd^=s#F+^==TfLwTBwB2bar@ORa|yH@LKHbW}?SO!jsN828S>( z8hyMft`Z@X2gnWW>}co*I4{flxWlnXd%&w3zct^+54Fq2MBI@fXD$K^zf5V z*Jy>J4KfuAXM**Jvx5~*Ax7tbHewpndt*v)0JqE^=}b z&!_FeUrm?OAI?VZ!z~32gr-9oq7#K2Om_yArZJ~Ck}0KiD2~}fqd;z3nc3N_tgPC< z!d8D*0OkLKc?t^>0R^4I%I&~YeR0{>OS^IS4U9b;`RKz+x3TLFNZLYwiTKwIGPkhb zxa3UYKtg(u&Q!I{FW6s2Z*T6$;S01!6*41TpVo}i_yMzLQ7SBUDY?{7vnBJ+by~lv z#|V>q+?JRf{HRd)HH&j`F7jyw8+Izizx>$EqUBj?)t`4HPImI(!v8k2f`3bCb-iw< z23_D)SC+cPo2Ga@3T(YY&|X`I27YG*?B|=0eT<*EhDP_I>;o-?2k4QoLgul2BFeFF z-)Qv_ZcQY(hrO1Tju-{2fvNcjEfQSiaErAzdg00A6ilF%<1@%e7YF+YeWTYwwoNNl<&5! zKuf?da*>_~iL@M{ZA3U03JxReRD@vra;(IlriGMu0PWk)=g+j)jZ0}yI{?NV;=MzMU@wZ(TUOQ zM+heguDM%iRMo{ji9OC)1R4f$mF?bcuH*0ie_Q}?$rj*=;g%Iv6F;51w}Kz;B;P#X z33b!gy;|Vj^3NB%ZnV*v6%UZpZ5U$LSZFr};cY$zT&w82OdyPe5U932?8gOmqTWq! zt%uzh?@JP7GVuq*MnOHc1wHtgW|rR^1sY!iaoE3wdyL_uR>lFbLZPqj_TBVj!FZirtKIx8 zQYKM@jDXFv;~^&OJ%Fa6hTdBIwRSaeiM!qURGNF6?>t5J&R0YQi8qA!uG>F5o>49SqX&TgkD{k=D; z{yL;YF3^61fba9a&!USj->O`RQkjdBINy39?maB5r!%h`(-B+Jf7vJQ*-3~a>fE`KUkrB}bd z9S1!i3uYQSW@PAnVpht($%d6pSihj(AGsB{McCGHqY7?*9uY6tNW|vIaqbklv zW!ta>l0S1P8OFeTF9TtfDK(?qTuS~PKDr`c{MjYGL(h*IKE)7 zulnzgGZ{Dpet!R+linvjI>ME`oF_56^gG-Y3q${*KU(Rla!ak>0Bwi^;_}BTVPBP` zqhWf?e%x4Yvu=~_o1d!YjBs~jZ1KM3xweA?jGFTA%WoT?(XjH(75KS zynF9+_C5C(V=%g_tJbP#`I>Vb`S}_V;(!ivoXV*7434X~-|fwesL`p%5u_=xesscl z@E~Fk^2YC82&M&*Mn&lgM`ylPqUxvRyr8dEQvRj1`A^enaGljDNA zI$3*zawT}@w-8CO_-%;#whxpH;Xw&Vy_FkMcW2|C60N%w_gY#Nwe4&l< zo&%-wk3n`nzu+6va5kf3{~zeWc`Jwm{mV5p96C9;to*;KP=2196F~$cyABAicRTX zB$@_4Z;3%)Ju6p!4l8>9cb}lu5ML#Wq?zrydpu4y-hX}PDi>-zfo3hMs;^qOC-H;A z_nn?sT+dXhF7{n?)9L{T12{kE#!35kosX=Z1v6KWH6>NPGVg+BqDdRv)BC(NaZ-!l z(VPc5Bc2{Hifg%I!cABhzIVVAtljV7EZF;eB_iJsX)`&%o6T!>%8F=(e9%YU~=K~2REFieAkm^_7w-M0f%+QUdptp*V8U&5}pjve}IfEf}UuFmhW2AQEdF8l5j=xnX`g5=(u zwFh)~{$L}BJ9}zB$@c>l5ZSc_!TmKa^Ggymsmd>3HOwb${E$69f1ebnp{Dj(rnQH5 zge*?%TR*VyzH@!x7Ce-Fl;D6_~gren#Mas@UnDc3u6 zJ%uj!@ylb;$t60ag`jDtJ_Q8@#ib{cb8Gwn_I)Ez*szq#76~Z2i&#p2pc+xY9;{9t zNHC)}!TLIr%g8qNvqp;$zR&pH`F6^`XpFBAfrE>rh(<6NjU4>p*d#MzQl9~SCf9ds zGHlDO>>C(jdEbjX@CGuZr<*0h22dYJeM+(HE6)%5vEEF8pnd6YA5eD)`+ z%Ua<)!J?uptJ=CUU7pIuc)Z5X4jHvGt1UxXm-3q@r8Fn6>bub z__Sq|t_UG1LHVSe&!YEi)Ukiua)k`S{ivYXNmva@X6v?m8 zR5E7(O8s<76IxAwMyKbS=klj4Yfr>zdb&h&`uS~Zufn~;B9VNEyL|GsMmIK|nmZ6M zzs14bi%88S4-J38KZ6_j2Tj>{Cnxc4QxEI3!KA}KC^tJ0nWygPVfh(q6kf@4e-Do3 z&t(Bwg)C?b@@cTPg6F+TJG88Wz|x%}hR2C10A>u|P*3bK3Y6F2=o$^=x?`D&F}xs- z>{4RssOgg|te=A)nwKeH(=pB#t2M=iNhjfC@3Y@7IgRqqgAP6jX)w7@eURaGf1R}7 z8HQ%4@+-0`(;!$wm;|2Va%E{vg(}m|Y!wHIk`&_1b}mJ*g?02T7V0o)qIH*ShuCW@ zZEhMWCJWCpv4*L2mL9^m=^46@5;FO%a)(60`))OE+p9utclR9hnNEVMxP z_ZG{>_ge|p5NRK4w266;oe93LHL=ZF9nby)hpY!=T{C;{PN^q=QBZ`EL zjkw&h`Hc$cAX>Z0mf-i^x<>N!^u&fIISwU+RjryH#ve{))jewY3ml-Z#ZF+5IM9uuw>@y}e0s{OR{Y)@2{>HsTfv-O- zQ^GziyV2G}z?+|kjzYU#u9rzj4I76nnrRj+bh64Olk zfES|q&gE6cx6K8j#p5$4r=iI>#Zr?~1iO*iczPPSkN5tkRMsk$;fNYi(XC&wY4=2f zMb0v^aG1=6V!T2K%I>zLlJ{R@O?WLw<00>)4f4HTxH?kXSHmO2JTIXzlVd_*i?6E9 zTuOD=&7gculV(59j@%q6f9fy|)B*pZ8#|c(pX2q!| z>UwT{5ZBNwgtM%eqx!B}LH1)D3xnj*e1_i|K-x9mBUgd-m+w=i-_}f_L{{$p!i%9q zOlX4AhWVZj^X&nW1Tk?xY}ygiq;bCM(P+LJPI~y&ecu3yjJLF`4kG^yHxeGAJTomLn@|J-%Bhlxz88*f%3%%4~EQDlxJLxpHHC zjasQNbS(^Vk;>y^6F8WDDYV?3u(C%_;U%9QBs0%NW|TO(=do~Up$%c;_ips$d|~eJ zeuwSbDT3&Hkc3-k%j_RSac9KudC|Ym_rfjsRgya{yJPB=Q@v)Li6X( z`523kWmj3l?_@aYr71m=qUmW8jut;2)^ZztBIk!CA$n{6>P18W5z^L#1ChQmwcXL% z}yix*g)oC#2TirZxR?#T?WHCv2N*>g)m?BjB{`x>RjdQ`;RhX?BoF zdXm&Z#p$zSy+~{iVQVU*@t`(@^Q7Xl9Y!@O4cZFC9JL!xpxIQ-^L$>}NV2GCQwt9d zM`_eK?#MU^kkleJZ=sUQFg^DIfG(3KJonkeuVu3j71gR*-mv8Zwm8dKCuU6S!Z{r8N*{|G$2Q{chagYH+1VX+L zq=;gI=wGOj_A+|TX)RwCD)#4Ey=^vDG8=d?T1w5$sBTnT)c+K1+*DYUc);n3PVnD( zEI-U)<1ue~nh%RMlVnrrSBvre={2L}s}Gu93I~}>qTC*j^3(l!lUnRU3FHe{)0}qO zrZVN37YfLQ2qa+Da0?<7?EU$MB9fed`uEJ_wLQ5oj zjc_Mk8||$WDik`7Q)1iW_P~hz^n}lw!!MDY{zwZa;#{YO*M(@`k24y`mvK>%$CH1I z1D0iFrUkZ#a+oZQtH*ku&lp}BxXECOdZXd1pf;(-4_q86pVak)8*y6tNQlYzen^+S zUuPqP;oF+YB#+nm#g3PY;b7_qLg^~=-O&6}AmwUbXIAQz6h-uL5V=&Aqqg}Yls+-k z6T#6i#X}q-=al}NE!OSXLNeGsTG`qgRHjoG?3H~#wHMH$?BcB7<*`5N{t^r6y}Q{X zaP;X^U_1#dOnLN)FSRDYW7<&f(>qTB=~n+A*w@WrpDe{MrVH7e3PNX_{6w%c_z=-TFefzplhu*WS%6l9BB(OJigCeSdtVNA05+aI1tQR&q87*F#>!+ ziaWrrWj-P%rHXlXTigw>b}$XVA~8Pl-%N@K4-3ubLVw#dal2o0xeiH=8f-qS%oBnC z9UtX;J0~>EA<#?ummo>fw!`hLZozb}M3v^Tha43tSWp#B&?gdvSAaM0z-(ZnSAWlV z$D!Y+b{z>XZRW)2gPvX-dujgj^t@mA)Cc5G%-~7~N(ggHDw2T1HYbI8(ZNyEX0btb zFu*2biKfr}RK}--Mc{BrJoo@vsajee!fc~pH86Z^f}jHDH$o{>fFtk}ll9c&qu{Lv zdiGf;?tiJusF1TNejSig7@%}of(^-D z1qv`RcF2?PYF7Yo#RB+uh&9OpfR=!co)GC<5m!?BZdz~wz~WU3<)#cW%%h#wVul%~ zgj(J;P!Kuk*_}|-y&Qmc0VU&@RVM9_{kaMWdW8tjKYEqq{TYPdDvscFXIKF_a40cL zQhKGj6e2#lH(SYg2x$bO^qH?{t@uZQDPqeq=1jlT?105}2mb@8mv74|-tj2_N_3;&3SQrl| zupd>0FZ%Ka!+tdU*ETH~6l3vm5dDiOU#3t~)8ozC>BQl7*n-ju-0h;XlIYlSZK2OY z*PlWVdbY(J)oq#zV^C7aoT7nQ`&s?U13r4>;YU>LRzJ874dZ%dvuO&gMq5&^x5tCl z1S|P4k>wnrFW~hy+vdFry!)g07%mYxr)hSHXrjVMd`>AInU8&<@@GgCahxRkX`DLt z?5J(l9wM8TWI1pgFT$5>kf;ZJ8Jk!AFqCSTG0cPbG|_#9^_g8_VsMt8`CVVcz&bdv24^D^FEU$NgXT{+X&Pf?oC(a4~WY10r423o;jV!p!?-G z>F;nYP_Qql0xy~ZIOJg1?RC7^=W)+xdh}?<^iJYpYWUO0FP4fJ@Kv?!Y=Ik+`5IS1 z)TL5L(=7`mdwd%Bn7+^Ql;P`%D~peyWGH<{elW+qC}~T3rH@;{Q|r?^f#N%YkzzBM z;bJCD>9LmE@7vz~-j_W-MM&_d$rV1du_|8U*v;lnS)Z-aK7Y|N_z1hfKzkgJqLjHb9OpTQ*b}g)EZROMJLej4p z0Ee}bePj}!to&-U-0Z`KPuiX*n_ll4u4aeq@J-3s@XhAWolV{-5Nn0s$wa@rsk0wX zkGNV5kS#ZU&G%e|M%8ir0DnhZ;NJQ{^JQV50(#{m6d=+VC@3kos`iJuc$*?Q-Ikdu ze|Gn$r32R>Z0F2Pjxn1ycltW><+LcGb@_CZ^8EQTUIn!s%VFL$iJ;cJFKTV{!=oi5 z@#6AI}1f796HgDtQ@=e`&dxmXS z8?Was7|6u-eEN2>(3My1bc&*E-$!#eEanj)osCsKFXC>I0`7r?coxAxyGV` zPkHOfHN3-Ps(=RHTgxd&{`+{I&*RX&={@PiqldH;$^JW)h4RcJm38k4bIh<_5j<3F zKG(L8da0FlG-jChekJ6-2jOvZegTXH0jnyOeW$~36wTBMqbp)f^j%9UTfbyVd zJe58ZXjO`!uC>G>+Z{#gF7A_tXzC#VeN+y#RJA`%hYql8S7LKhT8?M(s)sgVm<+-o z%cg_1YzeR#v?!8nWLQ=C(JJ=3yp?X}p>9^Jj8U ztumxv^TY|Us@ee2euPRC7bH$`o!=~tBbSIO*m${fc`>(RYr7s-F`sl|X3+47x!c2@ zo+2MOdZ^-aCYR4&&2KT?I4^U&+=N{2n%_LS#+kyeNml-zPQ;!#W3^lxmi*^A@=q*L zj2sL@#q>LMQc^g0+m^qs@KZ8Cq!7-)a~l{K@Ni7515X-ZjEMf1mc9HyXroPxjBe4C ze@lD)mok-R#mkJmK%$B}G8LCzy(Mm!ls5ow0H*paA5H{d+I`Uc{XXB`e$#RjI=Xpt zBxi8ojER7Sm;JA8-aHZN-1_Y*epE9N{oN30UAwo`D+sIPbQnW55Q}bep(T$&XJ4o{ zC|p96ETX1GajMt(8z6^Z!9+9#LwI|5rR6g1s25#_u{pjlHu*sp5K~zwlh=0xWQQym zTC>Xx!${(%8+ z>&7iOcYsL;F16%}*lv+Z677}nBl0+9!Q*lK>Ei_exq(^8 z3kKYeE0f3#`mh53!U0vAj}W@7k}e8!Hr9DwcEN8yqu_d`4Eq10!8sJj>>BrL7f)fv zJ9`Q30g|&`wPR*c!>oQ(a{yoyX7oi5>yo6O)EOI)PA3|dGD2gZQWmcc+BP1iwdm^; z7)KNej`M6mc2L#$)9`~5rGJZOuAYOjBD}^-RX=y=fD3LHxkKi#IrQe4zKUW!jv2oZhJX&!;tHe*s32SOyT`&z>TxU|u2L{xFlJlhGihIN=R zx-d>yiqh__dTDU;R`kgT9gZ@a0D*65jKg4q`5Y}JuFhAq*$RRIfKZX;;rJj&C?GJj z1E4c+q<}0JLFK!ISOYeQ4EsX(;Mf+8tn4*NaoRxmNZuQa>5tXs^XCmi zeU16v+(i4YXp7M$dmA5@urVKW+9?S!H6P>A!niy3gQ^kr?)&*{r;b$h^rg-IdE15& z4%sB{X-qCTs1(|^L{R-B1Z2)dCBIKCJgm2Q%pA{^dLZUO7r<(HlP zM;Q)8kTJ#@70SrwhzpJ+1I^=2{a>@`^}c-mR!NIQzux?VZ2MA2B9v+|4aGwSr7s&l zPV-q8m-q8O=g>k3BAO$+(%nXBPjO&^ZBob9Uf{vrTc!|In0$W{LOI`aO~ptGC-I`fYF&|fWzinnc}sZ^vVlAtP?exrOl?!?8E*0iOV|jO*FMj?Z?Is z#6M$kMG473_sBw93Sn+4Y*WkX@?|s_=tXzlh|YhF8}NDCdNhb7wQj=N(cuEbw1%O7 zE`s(M%J_vhCCDRil|{-nHwIu<6ILXxz3rHOsF^S1v%G}I8At2(^38{OFzNa$iN6Qu z-+z8aA@t&5lC^Sd-)?CQ_n}gVYT@It;3&MVb`Iy$^Yp zJ*Ka?6*CBf2z{F_RO75mmuD=o4_+c>y$$6R{SqCM^Kjscj;784tL3EnN#P;@c-g;; zq^T3oT2A2C%C1}DCInK1C<0)}bXavy=EuNJ{`6{zf?id}Vv`<vLa^{dmsj36#g7e}uTct|y>9nEQ`BX`tg?r~Y4$(WhDR6)kR?dY zTbWEE?j_({;>$Y9k4PIW{G-I5NeGwMs!hM|53jT8LLK-8_`c)lAp1#>3&TK-ak2X6Y|%E)Xit?uRyAlF(b*F7w6 zu`UjPN1X;z5Yj-e;e_-Dwt1_=5hnE+U&z;^!Pxwpeta85&9kK%ULT!r{xL!fRJcX% z{7g*IZ}&bMNv*nh{)H2GpDhJ-n;rL~V{T&Pzqh!VxyO_PtEw~mW7>f#xzPR7CL$py zMHw08));BLKr*8&oU9LGr1Tz$%6zHoD0DnsWU(gKaAuGmCSctaJ{ql~r|7;4$D%Vk zTfX&Q;#ko6#8V()n&&JF42z7>?b3xe<&Q#S_S6~zF2->}1D^j46UJEo(Uw)*2* z%l)JTv6oE4x_gahxXunoHu&z>JfvI4^ZD}+A9SCucJ@k8AJ{i?n)6qIZn^d{7{KPD zJY!UB^EV8xohkC7&Nd?kdH#$pG=y&hy<8eKpVe!~1T=K2qBVegAZ^ew$QhiMjlII zw33U|+njUJ9r${s`Jqcp!=sQ&T$$yK-w9KFQ#T<%4V2Om0<}OJUUH+13{~N|J0*B074rflOdi%fX1HDv@j#v*bv@1~+>Z*?Q%RW6 z-C|TVY7=xQ{FQD8$H$9SY_gTaVHWHzW?2cDHj9V_ynj13|AhV>ij4nRCdK;ZP+Ft- zAwyEqRZdQQcx)`O+1P|Lyh_|$QxY+AvI{8El2m!mS6pcBsTNV8lKJlky=+6TqZlv; zJ_qCg2Q->!4Ka|gxjXIM%cUI-GA$rK-XZvVNTD6o@>Z`BCkRTCw8o7Rm%NvNeeKwD zYIw*!L*KA2ZMQQ9>hFWsC8gyzRZ63dsBP!%&(1y&IaiJFJJ7X4*3;}?yZ8K+tDTVv z(em6l3jp|I1oV6FQq330jMKogmezXZb&doac0r#x*?Y-|9l+5k-k+~MEZON~F}McI zH`)QbsO1=mg>HMhyORy=PWoiADO^?yo#+$9R+ucpUx+FxZo_VD=E^K zxStuw%nPlUVP*lPhwGiqQ3A-4IN6lAW`XqOK#n65uih9k9(GeMqjweb2|yqB-$x}= z(gUUG=Xp)CuGS7!IsyLz$GU!W)ln9P78ECY@)4`LuwV01TWLx41SFVp^8z0-IW>COgx^r6f_q+hq9#1oVUt^ zk%(x?wypCTyB$hOD#ebYGVKN=Qok0Y?zdO?J_dwyqDVZBZ}xAoXn|jQf+rJbIBP~m zPS`Exb=;hgrN6U6k*2~fQjif3#T~thrm0N-`kfBa#Kr0hNmb9Q9(R1BzLrTuS8IR1 zzbIzVt`D=AKAa-|1vG{B>T4>8WgP{!cUC_a?DdbR+`Y)`>uP4MI%?_9VJ@(bC)ODK z%jf#hl9A|qHb;rh-;Hbfm@Bed2OP(#@q5+4;H;#oW=fJH)!yGcR1yPW>YbO0Yqe$( z8UsSZET%)FmedpyybHm|ERQoaleu!0KZH@N84ew{Y@X(|m4E5B)B}O7MDtD*&1t-M zD39YI30lTjR8xtuDo2~gM9=8Rn0oXC@B{}kiHb{}QIYc8zy5MpGw`q$)}(&3_(@bVaFOb`6PmA&i` zGhL}Kdx0J6*?sSqgFwB7&-WmlHR|$p@*Abezv2rZbRvW4G!zz5_wo*ic!;-ZdtTBDS!#|NbEM1%s zKHW>@(Z&}SvcUM0_||_vqeoDpH2T+%C|IER#&QdH1$jF3>EO{NtQj2}{y-^R2UK#m zc=GHj&4x{)s%W?9`2sS3UZ{lVjxsaZw95pnO9UjpRNW1VTz_Ht z75487{5voz@D~hhewVB|!g}5KprEOTlO3HEB0ai8zh$hWxm{k0aWK3-c-v%OT$c5} z@svV7dT7ig^%Y!LkPr8uTS88V75m&Ra%mKy_Q;H;N!7cWZsxe;tt~CoHT5=?k^dVA zBH;Q_N8Ao_$E$DEw<;1vi1>(g+lHe?rkn_~7?-_Fg(n&xZ>e#koDTO|pM(7@eznVzz7a+f%y~NKysuDb)!w2V$PpdfISA0&2|4;Dv?-)`+6^87Z zyvvZEvJ{3;?_fPHOs|zOTVoEY?FP7lX9b3nE!}1b0x?0E#!Y8p<-bqR{{*i&cqE(g zK2VxGjX z{p0zT3z{R0X?7g>zvS|_m^4fR@iykef<*q#E7D%WZYk<&rCdg+%1oGIUk~;hS{J3y zYsj`vgq!7+^Y{Pzkeo0N&?4Z88R^K;&}36|cVfFJQQw$=aV7ZqgD z6j+hG@iiG0e@xiPsPzd?@`xIa!#NBw$St{kGA=+THv9hxu1aq*wpWvJkNRKK8Kkf%vrWX~>7|jCM+k zgiU+-2toUWzrW#qxP6;VUoA^1E>}-2`DOj<|Eh$|7eYJk3iZbv8?8sZN%RUCNQT!< z<`p3sR`8X_nn<0+T&^a$M~91~;<8c4l^g1v}>+ zj6%3htPgu4j;k>5RYB-?*#Bdw{}B%w2GrR-3pI))IID*yS3m>44`Hev? z;bSz1A1OcgP0SOv61BY=KFO5tQC_!=G7J|x4Sb$j-oZbLz{Eck>>{P5X3V+aIhy6F zLBB2E2!i{3EaH`RlY?@*fAU z?+tfqeP@=FF67*dCyKHmTaX{mDKhTnTYZP8_Tf_MsV|A@s10-E zr@lBmUS=#g!UtBu5RSE$Tlz5wCn+15)LB2d1nfxxLf6Q*qsf(?hU@i}H)`wIDNAuO=^4IF>!#$)6Pb(gvD~oh!|Ek}b2b>) z6W&<7^3Y0a34W`)@0lel{S|u+bEui{-$s0k1WlreyjaS}j<$e5qnjf6@U)xOWO(FL zwRSLZhKPV1%7gHu5~^CV$5*Lt6E%iKE$f7xOs1Z1CbcJpweCm}g~~O?ij# zDow9L`GNh(`}vn}6dVF9AEyPUpG7;yPyg*46$sJM+F`}VeR8UZxTA@J3LQdwGuraI zn&zPOLNFCTU;qj_{~bws9X~Y+@p)=b(z!m%FQP$gFd6MGmIf`R@8Z?;3_E2Fd0WCg z>}v;dv@#^7vw;WWrY7zvy(|}ksWu7~=ZF}(Bv98YP7@z_*DQc<3^VsBRHwwXfR<-R+peKQ}0+BTZeuiTTv z>SO@{b>7(w_fy+QMdqKop;Tq2)fN{E#96=`q6jaQG~tZg-sr~vYZ3_u((vMJgPN#osQ7E2D{2{hk{#z6j~47P z&er5;r*1{=4hVRJtRf1%ChuI@Vr(lwEm7 z^lEv|#M&+(Bn5dob19%GJ@j3 zghZoUy}r3|{N=m;2WVPO0i*;7uO}UEDegCiS+#Amqrlp68P>wifWuK?vR?j$_x;K& zP|p=?WYnxq07hhfSuB*B$XrE((u8nyn0=Q4>e;WjS~v)QQ^EbwR0*%=ED4~11D+`v z1(2`Jwz|b?t=Hgw(JgBaM4cUNM@T4ypzt;EGrH=mod~TSU^ahD;PAN9S?>4)QCR5q zI&`@u^6Z78;5u|(&jDRqpc%2b3dNH^7&7e`#eD1(sMO@ZiBNG`q5!-OPujnM|N31WfEuFk`9JVOiYy3)RMv}*2o!# zJJS=I91hs7bY+U=aTL-&mr+FSBd0>cqf9Ex^N%cO%@`*~eo9bOP!I&Po)0WEAqNDfmM=pzzxU#r79Olg{yVEiIa+3p2!^RN7fOu4> zH_0AT`@_4vy}hYG#AL;q?_(MhGnN?|o^}UyTR^s47ovli|I_ zkZwet5{0UFktqR;;cX33vJAueFs6Rz*LU z=ynqo{b*NF;Z5Ak$xN;bZrRn7t0G))=e$Kb>xm0o>S{X}L!{+onuDf9Lf=TE6;j#t zhO4z3blTFWeRO7A6CuB9kWhFhww`J!%@fx_3};Z%NToUiNdO0AhP9ygdQ@@@}3 zch8)Nw&2SB{2Wu4j)_rnJ#4zgxSzPRHNBy~Z{*l5?*yw)H9MstCh!00f}a7-Go#~48Rx)z z(Mz}@0KxM`{M_q!x)6!OdUddZ^F1)s(FL`UC+0u{ZB2L4&KpT7-yR%{8jXjw++ZUC z@E;Wlylg4*T(p8%D145ljU9If72OYW;eP@p4sVVe)O8fn4xnKrR9_o1kM(4{=m&W4 zSx=Z7+%p#2a$tgy5FagaH48}V(_UMjVB?8RH z3UC7=Miq2!@20P1k5-iWcKT;4j9k;6>*>SC`LdNos%x0r6PGNFM)m=OWHjAaVtTbp zX?_5RTGS`;$9{3<%~nr{*+Ti+_DNW2=mMA>YujLMxy=KWkk2*7iGenrY1%X^CW!c^ z-Rs$yIrl0p`yAcpc-CyW#)OFwUXkz^OQSf;GpYsD;L~5b`idT5%2wyWZw> z02xX3(r8SxU#`X1zP73Skk8#+ONwT-Us8EHzWEWVeNX$+nRuA{^gU+E*vR>Z?QOU0J!3!z z4ALCCW2aV>EH;z&40040R6=I@--jP{-89P9ef~g`UM(#}YTU7(OeK^zA4^RQjJXWO z85??)sz-ZWEXBcY`vPMuSDve?*gRt?EXq6=qoedR#KyqPXcFA}LtC`M}41Hj)e4d37d{rM*f#e5wN+=wFI2dF*PhVzAUORc-@0|lA&Rhq~dv@Vq>te zV202|CRZ+#=bd$>Qcr~oP)^H3MIh86pp^Z}7cr;fEABhj|5)>@lK8fPnGyCgehW~zb$V_b#Xdndr$OYNjq_CUiq4749 z3#}l*1ptLG6F%Nv4bfcvYK1BOpgEj!%ET$&LwS+s3xJ>2DSLg^XZ++_&zDKR9 zMskz|i2NdvR9Cp!EjRgpb@C+xeLwO&HlcetZ)yI}uwmkN9P9<)E~B`On8Dkmr>Z1! z${Aqdl5Hzn1zr(FMaYF)3U#(SKU4%h`*mOI#kY-FClirntvGp+nMJXnFWkbA%BLu& z(_rg2d-vB7p8_M#^(OWJtwIiiu*HSNLxS{6ib--Y`o6bQGt0;d>dkckqji{WTeIeS z#2yA=77(h2V12;8F0II-ejB)YWYoCCjo+3(+)t-c2Kp37rv|WnrB)iILX8|utw{jJ zb4OU=8_>$$8nV9aYXl4bBifSWBMjoi4_dd~Qc`MX7BG-*;OMz((*I7@{VcbA zsr4L2^)?*eu*@>m7OBga7RrI_UCPw)l9u2%uG=%3nr*wD&Gw}|_V$b{^XBes#ccjJ zx6C`ES3?fj7@iW#JpNL8-FWCH+>V3NRjbewnE*YvHe%fz z&1l0RxQ+?ftV3BNdF*;uFKiSkmkjUj0d^&3EhhB3Qt|E!Cu}1m3WLIs*K>=+(8T+U|)lYi^Nub=ItCl+XFr0#9kCe5ZDPfM=zHfT?G zVyve!obaEp7v%EM)N;hzqdkd2=GPli?!_j>OHxpxAS1-r8cYR7y!)LT)(zgH`(+~k zrbOcs7!v}E4tZ3C2lcy^#PbreOuwVuv7oYY-Qz;n5}+hW(y;oS&S`@lflFT(iD!&t z2Sby}yk+k;_I?2lM#EBQUP!6g>4Y+gMPtBVO-z#{;|UM1oSNU=F(2QiRquCMrN5CA zQtN(yf!ae7nIJq`wmc|xM0A&NzCm7egT7oJwfO~XSL^v0{*hR@psSNYl28$j2e|cs zS-|I99b*zUW?ZpCK%_!)OZdvA(1-BUS}yrJ4Sr~?XxwJi#5H=liGH=DD`8q^CHcv5 z885$Yn>A(zw44icc9Bo0JsrMAhQQ)~gKOT@+a&64Mto%!R6wE>**XY&@k9Q7Sb4df z)makZhrPQ}XEA{JXuSie1PP3a3WrJyw!@ufoU+2OkS7nUykD>(Ij>VMbyY#SvhzRQSFN*zfjPcCDX0X9TP!tLr~hSge0M>^$Z% zpOx&;Avkn;uP2zZ_T=6yrmCR%=j(?yQH@{l55Q`Htf4Bln}H*x-^+in03d9S3iEr7 zfy7p!yoUqko6Tk?WURvlWU%AMpMH})-4&#!9)5h1dwn?i9|n(8kNJbVg>Hi_V?1Df zp!xC-l1g$NBe|f!^x1EAUN@EXj2Wl*4%oziO!57RIiQyN(l|~If!*mOhdHr(Q=E^L zEtp(l%tHXr+!s*_JkgKRWY}QVAXgC}_2#P@x7lbH@qYgJf%4teMk&;}sn) zb+_^R0MST^ENi?-2`?adkae?wcu|!@vHVvDD?*S!|8@~+F&W2JGm9Z{)4-bL+|IO| z{soypZG22CI=B5NBf1h{S#0=hwO0Ur)@$~BLil1_S)EI1C|6^-K^qv(iFL`PRT2$s z3%2H(=YY>?dSS_J_RK(7c05nGOU%eAcUrlU_~DP~aJD+B*=>%90Z&)stV2>wA|oZG zs&`UpvjIsu2uu%OBf^%G@`vK`fMjTAUIY`KZ;rnByj^3;EQ{NltZEL=q2SshP{U)N zcKmt8x)Otr?I~dVJsd;d>5pQ;`eG@c4MH9aR0%+HaK^}qcv2x3KFNOu67~oz*;`+k zNuK7%twrQQ&p$#R@YpZa0w%#E0bwrGsF-iz5fO&AFtJ?)iVs@>wnlQkMY3x84>8a# z8bq<2cMMv!Xin^9=U3QJjDiU6(Yq9zaEwg4Eza0xoNfEj_E$-;0fddzLPdIQPwl1Q z!$(9Py99JaV(#+HV&NXi8|^UAmrOk;JxW0Pi^X{jNt)~YO1f1N&kG*A?Xkk3b^OrJ zs~{A!dgD8i)j=vreDn3zrWb&sX=eBAJ*%?LJomBMrQ}RcOFUYvdHOp=5AVNS*J1csi+}e-16&043lRP z*f312HHrIoe;+%j28octufu)GcMs>+4cD#NrY0l5!1X2!A$PYSOmaA>2xG#+@L>Tq zlO7rI$s`KCmGT8{dHxREQYPnx@&;|$G9+|kfx#o|+2tmq-A-iu(sZ+8lD(wExkYLpE}!Shy+aF;R;XWG@bA` z+L43`VFS?V#6L_a_$8bbXMQ@+BkzARLTX;oryAcjk3;;7J69>d@9g;bcEWTVlWoiI z0#>72z-apQ8XNOSTeoNu-sAgBddR_Ka?rD>gc*o!NCvMfUdU(i8@*S}HAC&etT$fE zmIOO(O}ZzG$~9!d!=S?TkGt76JN2-6uqx$XyU7HR;aE3CGcfitRC=8+k$O7tXzrgw zLJi6N=fxWsj7>&liM|l5t8v&760C%J|7NfhoXz?oOziVwmiho+T2~Z+<@54TwcQLQ z{QwLJ^le~E56S*NvfeTvj%91x#v#FBBS8k2;1Jy1J-BOtK(OHM8ru%AM-{GfuSSj zaE2Hi(6CTg-Kc2{b;d_xiCRBGDs!MW3Wsh|OVmY-)R+|ReZ~y8*L7|C{bWW{w^B?K zyfk{|Cc$4Mu_b%ui(6A1fyp?J-9ok5u6|w`xt_OQgD470^^+rgyNJ!?psGwM+(ct!aX?`imEsj>Y zhwtO%IEnpoAEg3H3b~fwLAt-oI{frv#i^Efz;c3nv=beRI|*@2Epe6P!%@S=<_Lv^ z2CNDG;@QRBn|u0MJe?8vEL=bN!mP7)gWgcK?mOcd_$>Xh{6dpBSNhJ|$q1zMY(s6! z$|V0r{$df2q{x#nDmT++N=lcHRI{qx)JeW3+nCW9Lnz36tkdZDy>D7+=y5oVhJ(K^ zZ2*=DBcW&1TthlKuR0^P&#qfzRJ%r%9#uYFC^l7@{kETvmI+3}eog$V-mL*S_ue17 zi3tf5#9X5i1aK8co7&@m@4xFg^%<`g2dpD4qVF`e@$P6+K%lNqDB7uNS%F!iS)WAm zE7`&Ma}dn5!b@((7E(tt!(IOB{YU*+^i`>i5Pz9gq=?7`bh$<2SlCGVQL%0gJi>7K zx*m&h=epuz|I*|@M-dp-kvY_Gd;99++N1DC`!AeF-|*TMWT-5Ck~&I6u0pCTb9NhL zZa~mnu^YFZh4=ydrk{S}c+{02r#TDXK86lq}UiG3cSCAJQV2phwm8ZWD;4B55kAE&Zcj^3J{)d<6nDU zHLC8CsZ|;Ee&?W8E$1m55KhZhCE@3iTdUP_97KK-cjkon&^UJ=+~xx6{+i zHP2sRs=z18Q?cPXXm+{7~FjO-*z}#Adg& zD1y|WoUi*TSI7YR_IMnTtMsOr4y)Ah=1(hV2H7_6zu-e8XhIhUQkP1h>7|Ssaj4P> zssSKS1Rvl{alm-4GAKK-T+;AJG%&+~$0;WRMKU_EE)`Ju#J0eZYk&^O_<5R0NE#UU ztnK&5(FnR$L5As*cv}x+ISEW0#M(nK7xZjN%eTg}_>}s$k0WJ#?K(KrH55e%n*G(( z3>opt=Zv1*E4ra==C{JUFpQSHFDG1gd@KpHN?Aql*zQBE<0$EX@N%q)LiDyY(8!)9^-I;u-m9ooH0sAFz{OYrEpqY9HEkazRG+ts}Lf#hCL>`u+10^=22v8d08(D=#@IZuULM}AwMUL=iyUwNiyv>WnSMgKE5N$n1}UYpi%{z` z>fZ-fltOo%5%al>5^UYZqA_d|*aNMuNRW7jUbB$CuKBBv0~S}T0xb|qU<2`CE3z7b z70S1ZZ-!~hN%~vL6RWur+i|f|P{8Kocssz}P>RHtUrxgaI(!}tuZsA6@oGQz z+n8yRms^YH+{rdbEk*N8H!)Q8^vq_tm-!J=))p+Jr(bbx0G=6!#FEExCly7qMuQKD zfcnZ`8W8he)^)z~a=hU(#=msr;cz3Rj@RfVjzuGprgC$LNcO%5Ipv+vHmU4d(r<~#WcH( z<*zNQc7CkQ4gDcMWG$=G(hd}YokqM>W)veJ4F$Zjh||yDld^Px!pm$D!t)h(G=e_(nDWPrtX}V|?m{Ql(72 zkmGyaVa3MX?UbfHJmguS#A zPDs(g>#Z*w?OoTE4{5QU^sd59eCO?1uO4A)aks6rwK|}y@U-l-`0QxI!gGg7aQ{H>>v zcYEfy{SnXNincFQC}ZtojkGxJy1XO0#>r<3RSkEtvVO)ce(KUqE)iR64kcyKdId*v zE7FnXg1jm63=}OAWSnX=`fl!uGpNT>guq0O$GNG!%=pj8-M(k5@Ln%-W~8}>vHeC! z;;2D%Z_sG*pzY}Gg)nkGfs>89r0IFk=YGhUfxkr>Ea5K!CF#qBuFf}`;0ocZ-IVfyrwMODKQ1Dczl@Bc zp(FRUldb-wLYOEpq%^?FpJA#--$kNdR3Os_#o-KltAtJxgT{rLkHI-FL)DQa&q6}- z>-E+&6So)MK1iO}Gt=NIBh_Dr`-aoMKIvN0GdGrGNvPA!b$GgdE44L=JZZBTX!}6} zt3LAXO9OtQpE3#kXu)~)Jzw8+#38iMDl%WYm@bDILx(Df~S zj)eSoJlyl5VDD$)z9oI?`AN7eoY`(wA`;;W>;VeYfX;iDIsg9db3!)K^dGD=bI8e` zoIlF^1{CRVC}Pn_?3BQ>k?s4ixFOndKTUaV;aF|1}<*s zNmB3!6IM~6U^jvpsC@r`bds@)oG4ZZqWHaY)S5YaaE)?QZKUu>-=M9sVoh38B|}Y5 z#UfS)u3{I>dt)%qK}YJO!(~G3CQOKPOD{99flL#mVT}TJA@cU=lhFl2Z-l>CSzh}? zSi2wc5z<)GB5uYgXi=}jKAVlW2Usz!}zgZ$RAvocf zZu6$!`m{M9!x3dyH&dFLW0#c85NH9D{qB>SO2nCf$INuklhsM;%H;d!^~iQ&o8XU6wzsrFK;0&(!BX(V{A8OiMmOS+|9$V6zL zwy{vNu9P4S#W_??F5!#K{geBP_~9`q&%$wk7XMpj-|I7<5$wO(yR}=WIs=C3Gk$8i z;Eo1bvV6_@=JO>%iF3cfN+V&)k+;h(co5bXNynhh^E5f_gnPzY zO?!Z->+p7+!z@=*A}`-1k+y>?%)zrnT=j3LPZ$R7LJMmWIHX$Oq}m4Ul8-hPwr(YH zqeZ0_oRk`rHJ4z5MoY5}CWqq6-0-ZyxJ-;%y6rg3Ik)Ri&uRCzXR<=8FPcZ$3(35Y zAjQ~AQR*F#?abzDdl<=@+(mQxJDKO<>66~-gY^5rgG$hPfscZRWBSG!y~S&jQ~8dE zW9KRYJi}r_!)z~Qo}KEt5y;$e4btzb-IL3@1n)`(+ff>S`gQ8k8lxrCzb^913)v6GEkeNh1&P2R@uvitq_#M+Jw0CPk31 z2dSeSa+HLI)MgkZ;P6?k%s-`3LwcBvDpE_3Ol0X+Huq3%LzC=Bz(&Eise1ePfF#WE ztWPsqtU@^Hqc1L2No{0=BEpkMHR3`3Uf@A^R`uMa`z3x&$akU2SGs-mw_@VAddlCE zcHw*porHX~EH%MCM=A43*~hoO2lr^2$_e9=ozBM$r6i?DH_r$%7;1v@Jn}53OSJ;N z&6XU@SbALYVzeh+IjR=pyUsEd5uq|pl5WSdpToRP4r4v+rS87>lbak@BYD9Mq^?wqjH_~< zZVHYjW8AE9CA`Z{G9K2gc`8B#>o(kNl_>cx*SfF7lkQqAJ`HkO{+vbtV-|q7b>*-g zqc1Y%ltFJb-~69G63An9^8`SJ!64dDggT^RXKqb_97f+%h9%YhdIE=BG-PPLG?4_kg2L>*@vV zhnj9(X<}%tBPYKwQ>tCr;feSZ-s0r{$L4b5VO*TLjZU}BhRq2Xgz>WDi&E{i6FJB$ zvqeagiN=wI5RxQp5OHG$3}5%lLX!ghl~7AH3o;8i@YqINpY;YA-g3V+kQVR69w{7Foh(XA8@pWDTUNPb-9-McSk#=*+rE!B47eCo^PVKrklS5-528_yo~X zH>@SQ^U6RkdyK=p<-4pp`ctb-IidOXrTzSsD6Fm&Ecx~y-$dLehyR(|e`dkoz%m<* zys?0(qgN-0Y=|RfmpVB@3FnGm%><;>W~zQ|@an+YKlgR|-%G-Ozhwk${3F zvW4M1gJ*hrPF3C2d)(oz>@vN^H07UB{|_?$o5in>gsPu+>m}fEEf~lfh$_vG2Xqk! zn8%`3iePgNl(`*4PS?o)6=an8K_$%BeWoy`Kt#StZMN`3i%a@E35XkFnZIa%DDi%2uy!i0x;_j1;4&S`fZ0LS`FmdDL#L zysb8SLoB9-;stBWg*~&Jl#E>v<{N$SG*2yw#U(GZZY*e2{AItQ!8O_Ue^hOM%PjzI z6|>r=TJk6aRqWkR>E?19{#w7%4jv%A-W{d-*{oINXXe0UV=opQLPNUkajMtdb7c9C z$tLV9E{ty-^o{r1UKl3FU6V(dP71KVl)IUrO=7LRlpX1t&e4OJ_KKW2_dXTD(x8nx z@_!;|$OkR!?}n7iM^zTSwf*PMF-k$TbA>#FD6jt+jDlbGHRo_;LOa!MAWaw}2l;vAn zme(0UED~kQLJYoqVIiu#{;2*}wUw0|xnZ!5gB$A8uVGzw!E&K2TE^VR;q~xYasu5O z(R*-@rqx7kb)g3p+6Rux&f@BSE*TuS(4iFp1yX^o(7HTZ)~qQ70ZF{T+g)&l)rFNE zVgFP3;?BGfp%&+_IGrvPFSogrF`c}fJOQEfnBjF<|NP~U z?l+QtJX%2x5Bv+vDtFrAq~aey1gxR13jMSv<&}t{{n}R51nu+prQJL=nq7qy5-oZw zt!nX5aCt}0N3-B9FA?#PM;@pc^?%7c(nE#6(-B9U0praH^`6&pPDPCWZtpYM0T`U8 zRYo?iu53 z`met9Qtat9+bdG7VFsLOO48$pR|i!!l85)cC#2i zNeafS9PrR6t&jei3MxOY*x`ua=;lJEoHDAvD3+?`TU5SR=aCl=3oMjNjM>}W%5wli zxLvEQ2)LM32>zBVZdi*66O9Q#h_6}q(UJZ8dBrudO()&euTcSHbGqX{^({#T~i@eBw!gp0m+yc9nD8(CK~n)L>kgj`s+lnx?sxQ z4hqV#!+w)or`4(DKub$YoS3(vsMeIZ2tb`uW(c2Fex34J{yJ%C@O^=nmFp{C=VkZ0 z#Q>aTkjy`P7);cshK>9aEjMf^paMf{>|-M3z+ru`j94+;=h|;I#ePdujd}d51_s){ zNn*^y-6QMXwtY5YND(h=kX7>a$?c2irZ=E7pM@=Q+r%b47 zniwJR@#JUhT^J_Wv~eyCq|`M-LkW&Ipt6b#blwH<_K>oNheu&tt7gY`1jPP*A^CA} zq|kyF)}h{7qZ;IEg$>?hA|N^8kPTSGDpQ%o7yaX|}vc&c_K^S&Wp+N%ZIVyC-WMp=<8@`hY+lTh2*D zL?n$8aHGlxRNn8bx-xm4Tg8v;Z!e7RZ@0A@K1}EU76oq~iAvA0&D;(6vSN;qRG)#Y zs6^Ll zU*|0MC-t5BP!o8V!iCJz(zSyAtX~$;(C1{d@OSM=+a}p7<2k}>Pse1XNxZrYCDW>t z6Yufqj=1~eHZ+6igKH%}q5md9AF_N)+FS)>5fw?QoSd6-mK4y>9$h_5ZV1(e&D!#F zxzPzZ6BnxVX{x`>I9T(~9y{3DG67Um9N?5ozD#aVO&0asVkOQ}cevg~lI3bZ!X+F1 zeuM&NnGK#QOuF#1*Bj|#2e7uKSH2e=eFp868E+{=Rp{5iqpCaWwIvG6_R&LwZFRvk zh8;DHx#T8I?TIFGfreF5NIKVSX?yX|8Y_V*X1!nQM`Dta-!3?PlT6w+ooB9sFb7RGAuv?E_e~nUzNYWkyl%#s5${L4x?K>ov zIR6CROcA&XqQS1zZU=)z?5UrYsqLJA6s!sciAw#Gnj$YK9H|9?HB7xEf%iLA0T1ff z!N#_-_9=n+uVRhEC|IgRG*A{(g(^UeDWuz8?lA(u>QDUg)?!4f^$t%i*U``&#<0P* zG$2zLp_m2nlKbg4+#7n`?GGD|$om1Vzb&ZG4$ljf35FKEdI@_()+_*3I{x@P^2#kK zX7OGp`wSh#uePV-l^iRfqjkBlAIVG3q$12;5n7`G;&)R%B4R#Xz+4ys2#qnLS9cn<8Wtzr@Zqp36`$?XjDf@XswYk%f078{(^e#o zd=*J9nHa*D9jHs-U<*O3sLlC!OSdlR5q2F|&B9k!el!xA)U39Arg?&-#7qO@PDMZ9 zvDr&Sz7C*aFQ{)JBKX|)P)&V(kE`6Y`%B$1B+V!wue^S%wP z_IOD9!{7sY(l^klao}qCi-KuadlIhGS*s_LXZ^gIweH=YSf?A&~wa zaQf~cl+0}SwzJ}bQNrTY&0ku*g3oTnw}Ip_`T3&Y7a}fO`8UVhUdBLj1}NCegjU@J z(2l3QQ(~9fA2|KZzVDKvUsDZ843h|WB*sof6Z1(}V5l~~md|txzFo>^N(TV^X*Ucs zt?jesZ4JQfgcS&lB1mjI8y3isf{1-dVy;r9RG_gF)fX1cl*b07n^RsXYQ-Zf#0rO!TeGipL=k4g;S2JyCn4p z)_flHJ#IHcgDSoL@siWkf|DX)pzB+toMjFY_0tIMA2%ApFf}2H2`cv@eKJrPIUk{Nw z{Ah?tO*|)St|EPTetG~bB2sQ|%G>MI@S5ZJFS-RB*U73qB-0af;C;5?;e#Ec8>V4> zZzm`=2zM_l8?QkQ;=O_f%NZi_Oi-20lILA6QG}WU6MQ>ej+m`)-33FsFU1!OL6?+s92#kjwMz z>DhlAC`kAIGDg|-duL*}6Du=!jylcH8)hjlYG}vKxb`UYwc(0X^Zcwtr^HM|h%Cp| z@nTB0_b>R`!hn-}3Q6H?QiRkm&Ud@pRA|J;c0cW%5GKhouu2Mj2q4mf`7nMfe)KBK?-kNM0Jjc^ zb7_V(;G%1z2>xcXNAOZbe+A`?H5cb;>wD;z@-a9QIkgx8++^I*`rXlZ8N*>S>jtoMF!0mv@2gn12e zgcFQh!!kF-N{Wi2RE)Hg6gfc3B{ZUo|CZgh;D)snBpv+_`DihjIT2LN)TZPm1_>P? z3akV+Ur9g7&Thi3lu@9O#mH~)KRp)22qu5CKksmrd+5&~$JhsWauq#JVNPuL%J;F~ z3hMAnH(eJxJkfr-u5;}O_L)RO;~u^3x(iYYt0U;9A%f=4>&RCO7d>C=pgf-vG8Ctf zM8VchTB$Wf^oES+*?xXcDSO?&(vU6SS(+{*kthLO&VKs2+CFlyA`=_ zy5x2o9zv&~cVn8q-r%VD#86V>-~J4pAh=uqBj>!@fd>bWb)Qwdl$}khpG9qPq+j?b z_T|-X<&B{@xF!i|f+CdrW_#DWzZP+nxfZ$h*7P=50~ z?}%iai`+@#H)F+ss?BCUl%&=FD?MsX*v83eJ_R`6vng*=F!5$mdL8ARY8qLK}W&FH|s^kA1C41CASku)Y3F) z&5K_fh<5^#WLP%Zcat+7AHLwIDGMFc0}%8ZB1$G!hYAxyUmUR8q{(!o2&$gsn#VEm zuVVv6dQG3Vfd_|k7GS8$550D9JrndZ?+6?KC7!>SfkEfvGjqokmQ@>|`qFpTltSqx z1Ej!qTtEKC;co9Sg}HjBN>cqk3ymHd9V<7h@dD-9g!>4-*D}%VXrdiP#k7_tvOpnOlMFZ zNNKxVH?SD>ppQ1g*Y5L#@D1d8QQ4Y~>lv#j&-UJ$P!H4S6^PH}OHPg_R6Ia<8m#Z@ zT{@cEe{sXmqQaThbq?KaF&%Pw-VnQlIvn)tzCPA(vK2`9an|W$%Y$DC>?ZsVBHdy! zVtw0R2`iHaKJqyq(FVc{lXeS0?fS8hH`f7*oN(~@`BE*c?rnAQz3>9+&sOK%CZf=% zR%q*3x8~?vy-LEPXfEPuJbI^%HvxVRgm89?N&=R|aWC%s*TyMtr{)p%!6Mhl9_t0z zXmY`Yy{>OW&KV(n!iaDLb{-fQnwTNOn$98wD8elG>OZduMny8z67SX~(ZZqNLe$KgC@W2r%hZ#NNQM`t7L^SY100{lHF;yBRN86qD%t|K~&5y%3C zn(w2Jyk`X%dJn!EXDQ;cSfMfDAlcGH^4nK25jIE5*oE~K%G^?$wkV*IHl|L$GPJ>X zdpkpotfd0GpceZEZXR!_2EUKDvZo_CNqv#Vy}1*>tZN@_SH3Yy;vFDup>NfC-Wbfh zx!;_Bh-B`h^VueiO6@ORvT|SA-*GaxHd4C3%*io|=TZ%0myT^EZRQpMK~rMfz%Cfw zuGYUk;p;M}_=-{lx2mLdz+w5*DW-{R8rupp))1qe)dBtHm6fr3C^9y6Z?dM;PE0#y zgBOoX%`1=fJj4sQGr$1pMqi!X_oru-$fL$vCdxjHGZv;jw+v{um|s!3cSoR`*Pkh; zLB^Gf9<2lL)_iSLuk`FUl%lxAk#x{5B!&5G7ptNgpuCPH|!_W9VkmwDA&vTnT`K!R;U}pXK7kL6?WHvfs zq|FL&sQ!~KkAQM*zW?%TY}evrp>7hb!!lZ2BE_6?I8XA?AZ#+%>b5wab=4cZV7ISQ zmFu+b1hXbk3gg+LFFv=Zt&j~%f*D5)oRq)jrg34dF8xX@pL+(b%%3$ACvh*KQ9Wl% zHTssKiYkW-Kko}zyoh>SEv-0d7L0V_bjVZ41Qd_@f8X12!nOX2^;zq@6@VCha(Fg)g&O1hIO)$9fSCDZ1B)jJPG31bT{60oqC zG!I$j7H}DZ)%If|ZL1;OT16cuJfjCx_c#I$XgT^Anao{!RGoXIc3MA8kdz+b!UsqZ z&7r0U?K0&7*w9RFH>I7DSnsVY{7WP=h^%uX_PrNN8g>E_t36b>_>;(j(bF#oTpGm; zB5aHqH2$`duhZ8p)T|5K{CwVzci|U{)@Oq10WkQ1)wkQE_#Ua^@AI>}d9;OB z#Fz0&{ZofRb3Y*_d&AwTQFK9kES!&fa|AL&i`MX(^Ngpx`cM}mL9HTLl2HV216Ke? zgoN{(rh$N}PpWg}?Pu4#lO+ldvEOWePRg;a(ZJH)q>q|b7ZSmC0IjK_t6DzfKWfDP z$ZJcBENN}@)I6o8T(%Vtt`^%|w zn&(q+D`|?`6B)Yo5pZT1aE1r$d=>71V@^c1fJTx^mWq;m{{RUR$P2uw`*6FhG}bSn z)r46S&8~sqpTn!CmYcE%?YjX8`)~KP=Hap3wgZpC{b8hQibK(WgH2&HcvsU2bk$kD zCOaIswjRNf$;BK&<|M%bSaHafakBK=Q7`XI<$jVvE-y|tLjA#v%X&dvWji`lk?3g0Ne5v(01=Pn8SC3<1#fcZcdnJutl|j^C!XX{ z0Cug20RgW|KqC_~r?A6D+M%^Q~8= z{4ZW7OPccUq}Wvb5vaciLXQo+?RT`H9f<9y5zs-D^twg?WYi<%v`?~KG~m2-2^u-l{Jge-u`au6{$p)sC z>R{ElqttNMwxj7A&sDiDs|QmL#&a=(_Its5-(ye_`~%+HEQR5$ru&MO>6(U)nJ zlO5)h*EPw(R^!dg1C3C@%i)EbNA60cD#uRJN2pNY(9l<5H3+7?G># zrm$dqmRu5D^L?3io#6)TL5~B{eXSz#z#)}$vi(pfSV*`;&@~TBz2mn2UiycZ=+^8G7&gE0gjqG=tVp z@7d#H_`PIzodu{*$u`s<<(NCjVWZqS$j}4w4HNn)`6_H-cm~AS!fg^`HwlKd9uYHQ z(O}aDB2yyT{IODjW7KFy$k+Gl*z~|=W*Jcb;l&3(R6#_*A?5HedsuLl6r&OXtL`vy zM|XU~xmm`7!gRp4JbvA9m81*lt1lfsIn@uI$LbVjL$Ws_N^l!!O_j^_``|Khyuk*g z;pSZgE51|;Kfm=iK9O4VB&mnz@++8UpN;eO&&Xe?uDNv~&ZFOuq?!u$0&MQ2JKmi+ zxi#aWu6yzK9NH-BRB^8nDm)p!z81n7Q4HzBYCx-4w&! zN6BGA>R~b5U2B4V?LR5^TphA*?^q|Xio_ee>&e6f)<`Ubd^Y0)5EJXZV)uK@EZFil zhwR{u>p;u%-;;bDx~6*~kZ?4{LrA**o?ooVA|@ZG?$NZG&9e~H9ciT393ir2zl2n^ zW=mFs6uWpIxmvqsDDSV@yJq_Y&Wc;D(&9ryt#?^_A;A~=A#(k9`%;?TV8^TU??oxm z$y8InLGv`y*CsHk%|<;Xb=2IeiUeR;eA!<{#_wSwxaBR|_R`DUdN<1J`iU6K4q{zT zmfjwFnnGy3>N&m;d%o3Gj2TJaYI8%iZ|*UgCEbV)-uVRUzP5{MF=!7B4{x zdqkM4aciKs7jEppVoD^o1~OVGgKZ}IW46c;NjuPx%V~&U3(z*2A-f`uAZQp?ct?%o z-13qSGiVs}s5;qo-l0|7yc3m14Fc%L?lE~E(j9&-E=>f28r(i}_#M3RJ<<&>#~Fz} zGh%0y06zhUUXv}c?}b%zMK{cBRTmXQIhXmU0zc0$q$hBUZ?Wr|yQZKC+zq~#f9(-s zji!4dCY;^1w_;IrGa_)UZ>v(bTQukfd~y=#J4q+c9*g-mth6CjKHYA^u_6K6U*6Og zcEEZz`vBcv*9U+gj*QqM)8A#dqI>ae+q)3)O-~U@7ivXM^s6~q^yA{Ln>BW_Caq_z z({eVld(VrSK^~a3{*%>~j4ta7uCIhgs7-VYBDXmOQ663A2XLBy^TcC%=$d$1(}td> z3hrV-oFTh({wdF&*4ibG0@<5sO4om5#?AqexSctE{!OFDtO(D4+X0kvBKfs>ykM8@ zuxk4ZhR9oqU9&F6y!YVGDnERCe)9Ioa#=|V=Z;sK}hNF*%8>qBxn zzOL{h|ZVs2iWKgfFxnwd=HgYS|*AZxA66 z6`zGC?Msj}jEtEw?rUk<0)|gOJ)=C^qk8?svyFK?;-`9JgWU&S4d0*do}3k&r&ikV zX0TTbQv@ceO{4CYT>454`POWK_KhDC&T$LMCqoX~E#9Wb{4? zzI=q}5fiP%3SG#nZfRtzPh^@`%;h^t!mC(Z%PcakS$)S53B9gG@$wjEr!C~KEI8f2 zJhY%*hc-)l0&VVw7u8tbifVVmu5kDq%4tS?!vt@{NEC2~CNC_Dn(7X^K#cQI9}k^l zX_Ij7Ov!`bU8fL>q`VLoCWwO=7W@Paog{k~DmnnmP2EUAZ|)V=$>7s>y%UDFJ46HX+X_nP}Kg><9r zr-bi&h5Bz&t=@=fz}$_i>b!rwT=G)Yb{6LY{{qzFX5yqkWCUAl+_m)fTjCz7c5wA{H?7C?9_>C)Hvp=??L4m=8FxblS zC}0(4Zh2c{tf1$JCZklAzIMqKe5qm2!y>$a&Y<#US%hDI=%sE8e<-1hpr&G4!U9kx z|JbS(WtWP**%#7z8w>Y|vroq_j(st{K(nitN826%@;wMH$te^CL9vwLT!IC@m3Vuv z@ilnN%jyiQ%pMA7$teiq3f&)2EZp+|6IZ zy~pQzmgrA%=nT#@4DpSI7#T*ok>VcmCI)(l$Pp!DKGVV)8>d8{NTX}B82i0Pwj*WV#jF}-f@OyF7k+Ch3MO_8QFdyC5S!t+(EX8 z%!}g@cR%_{FSz#J85>hmf4qf?{YJ$;MB1F1Xm+GJvcCxZghBA2vqo0t{eb4^YoUrp zoqJ!*;5B6>`1~0?;WcyC)DogF|4t(-AVuS84yen5e1$d+EgMbDj1nW$*bzFWQd=2;ZR)a|Tc=;)}g6Are!6JVESY*lzMOMNbnpMb8#SUv+hznDX%w z>-i=y-=*L7;nbB|De)M0wU90s^X2|aQ5>8>jpN`(YR=Ggn2?q;oj;tOjDwf3`3={; z2kLPLERhw|-q5c4`s_f|Jf$@GSITSy9&&)=yC)e;nLG;{Y-S}s(ixJ`89kI2oHu5xoObQ0j*6}=x)`{Z|JT;g6r zAtiPi>6OrEYKngkxoz$x!B*ke?`bp%{meaF0r%ss-?ve(YrPL0?eP2E^^hXTdNRA#ZW~6!W`P^rt$)QQbZkV<*#?dv-6I3RFVHPkDdJj$R>1a51+0BY{ILp4pLI zw-NU3c2Z_cx+Z9G;wT{bGVnAr?Qpq2Ic|C!DH5+q)K1)IMFKVUIvw`QFUtB0Z&Wy} zONs0&26n66G`?#fEVfJNFs`7#Bg0m3_ap5D5mZjLJLFC$A9<;6K3Kbz6%1a_QBFveOiZyOcm8p?k_p@MiB7XSDctX6_*hS93*N zbIGHXbsYKknhRjTIh4VWbYiFZhsF8@qDYBl#e^6fBF;hu-}~n<>ry!tnzSv_YVSiw z;)-_)IaZIw{p8r%>EyP&?ux1QNX$&9^bfPlS~beJk)jVCqi^R9yJ4jwIN#jpmbI(i z(e!a7G>^=3?JTOfTV&XMJ8XzfE1aUN+9MeN*VA#mUS>r9v+e%{4-@TBw51;n4rnNf zh^zdPhV?o2;)hHE_4PjD!;_a_qCnZwMqZWIRLLpWpKK3$vWg~l`4vKK79v7WDgEZC zWk^>ko2HAMGmpTx8}CtcX;oAbOto<>r}4liN8*XEdVP{Iq>$SsWzcCwd9ib2TWNcU ztA)MwUp)PpWEfIVg@hzD>6{bZxnazv?>zcHh9wStu6_4k6s$B9>T_-3dW?h9g2X=Us^TScI&ctQF>Y(>R(undI;>&YMCFHWlV}98agWXX3ZOOC}yJ z%m&FCXC7r^Wd6U~&TXWC8ZGm}8CqIA#il!BAWbZ!firiH!(_b~E!FL^B?fF2CsaGfV50aY_0!vd<(|59NSZPl<4<)^iNPayh~%IVPKb|OgfXb zAejkUcXX8L&)*&X6#q{T<(hS}*jbpMG`BBBmv1wYw%XpQv{Z!i2zIg>2@4VMd7xYy z#T%$XYKM2yWNDIAs0mobvI95vjMdTG!jBtEPsPLii;a~; zeEw0!!pn#%rb}&KkCcT(napz7v>Gn#*+9el{~P}Q;L^EN(wh_0EdwN4e593MgIbK1XK1pWgHRtONLTd^r_ zKyvh2sDB+U9d;7VL)g0ijt6fDYwI(l_A-}^MH_q^ycC_^R{HDTT(}VuyA)h+@nWZo z-kptpzs`O|xVs=W_>uoP6{GOGkR(n{g64gt5lBlLPsCqkLT&1 z1Mr`x@4pu+%yxfNZ;uv({mZg{Dm^t)kJaClh9QZj)-Cjxn|KW#Rr*IQrvDu|H6#}8 zD6qOkI`Q<6W}(mr6z+)ldg?uguUaV5Nx3+#CS?NAB!88g{xz!$`rq$jljgeYuudLh z>g?7#eu>SpP#Bj$NGb#2Gm#qPHTO4iM;R{2S^e+Vx-BB1zK;nSn`;nOWL(|0gdGfx zs_#{Vx6m({?zIlTr;;Tz=T4VwD)^t_nO?;V`>fp*J!{&>6PeAynlfR0^f3#H!PtPs z+rF22l2wm#=(|KD$Q;!%q?_%(Bl9;y>IK(pmef*duRmn5n^JAAlx(jilz}n@+^S-% zFczk~oW@~%>G(3TdX2u$U-W$cTq}V0n-4B=QnV%&@+NyaKGI_Rib(VKa?wH%fjknD z&R?}?v7C#Cm(@CaXyb?x%3Q?8&H8Uoa^NS#;9%I8;^B!jJA!^WLRA%fXyC#4j0+DF zUSqV<+iW`ATE3BjF1q26=r$>YyR7)X0RD#{Kc-(n(%k5`?xe=IyaYYGKEB7Xy8HM2?D+Z7C2=4R*%-%H z3UVXxFWro8@>`oQvAybYq13g&QGJE0S}kGL1w-WRo3~54of!j&cH`dSo6bY!Ypo_dS%_5bxF$43;q1YA>BGgvD3|_M-wTEPPZRZ3wAtkV%F~` z6B7$QAiFY^D0p(I?8i?!*{1}4$#3f1`$q7Oh(#~jomSPowW3KgG<7Y{U1=b${6ZAg zLIdw~Kl)9gtD0S3KwEX3^Oi)_{VheQ=;fHGjJvi1{L@`Q+rdi4`R;wn$#9y}Sy*~AF%f(*zgt3rGLc4h9&QsC# z$$3~i_@58&_5{;jFWPrGQG&agS#<+?lAgWU08Ys9!@hRCDA~sC%%Pb5_}EST6{R{1 z3C7iUzYR~Vb)ZLQ%eD9Y;9%EuLwtBcTUCV1yt=Zv0V8qhdRCPonC6E@V**~l-k5Gs z^82<`N_9d;p%gP8AGhND<-HWER{3oUotQl=lIyFV&#HD^liX9qYsM$N?t30tQSvRx zX>#{A7sJ#FOl&$je_{w0rja=tdLS=W>*sxFp_l2q z54Ou5w-=9Oa}U9QgSf}I{lABxJ# z^z9C>6{UPEs&^F@xK-G?oFK9|G%Js63Mp2PY7kCW-|us-4{{w(yxaU49-+0!`)S9{ zoNRgjA6aL?5LdQsYuq(haEIXTZowf00t9ynu0acThv4oIG`IzKmjn;)?od#`+ueQo zy!-C^1yy_PwU^BKjWNbuk$gENvsBs~vmxA~WFuD#4WP7=%V+MKe^y>A1-Rh8z-ij( zASZ;1!a`Hu8Tmw54$Dmn+rVz0bfqQ=l@7tndb1hbHbZk|T9(E7z)%k48WYZHjKvf4 zQ}7QNSYVdCsMPaLCge>yPcIRe+YkbEuslpLJ@Mbaf1@DVtu$pfIjm#1Pu`jhL?+(= zd5UmLMe9?u$%XlCJB;2giiVn+bf=AgomZMwg_fUEg=STx{y7_c`n(XKyP9dE>*2@c zTH_$Iis!l?oLH*JzEalTu(l1VzJGtinX4k$<)!(x8Y(2&F3Nv`y|E-G<*FZwbg`e0(+Z7(x#{EuKW6re8jyEYETxmC6tZ_jGvZ9mA zQX&zy6G#+xyxg$kmZ($GNg=lo=BZ@5Csw!lV4$UJiuyR~Fuy9l-5#6IDnlw$(OKi< zc~ov$R#FnGB6Nz2S0x_@z$*oQ&&LvNFOTPH#>V+*EkI3=FF7S;Ie}kJ`N8V>1~^NLfXEW!BVu zjsJva8}8Tq$k8~$oLMio>U^D}#PyV%%?ssS-QYA-W_u-@kDG4Y{doXekg4(EgPbgx zCqIx(%5nvbX%4Q=%kyt@KYc^o!EvCH$s1z+E*vNsIGye12Z8GNkyhH=GU3pPxYV&lEfZBuovxScF;3xqel=RFkM?KLZ!=`=rGKyb zNMrL$_gbdk^=gtA^u43+)}Hy*Y%zqX+_Q*rF0RJu`LWqy-9WC6ye8k}TfO}sFE4-} zwgQ}uIXS8;D=SlXLI^BoP}#;UW`YPDgyd%%OG^FSRqk{;AiP z(YdgTOSBBi3ohc~w;@v_Az;ywE_@$@8v_PRehp+`z zxhpz5?0OpNcERSg1%PK585r_`Na3jFl=Gp>sm8HhHhlr2@2FR5;9m0zv8@M{=ed~k z(%UJn5&EPSE4F?uEnQvuV3nHNG-lP?Iez{x2AhwMP7s#~Y7)ce=N7LV=1LARu~#!E zwez+OQ5F20akglE?askbX`hLOxC-!wGG_0bQ~1;4vCH9MX>K9clyEZ8#8ac&zJ{!K^Qy8Agx34 zwDF&}^D8Fb?{?V?WdNJ*sDYt=LYx))9FIt#ENP>SdKOBVR@Hr8S+7z!l7tyGaK6%v zd3!jQ33`3XJOHlgGl1ivNlQP~(m&co?oH1(*t;ILk_Ig8V5M7l@)D)X%h7ung-TEX}8$UQ-%ZJ|@X<8I9)tkz` z(z+XNoCYl9?u*i3Z$LN_ocDAJCtzgQ_SNCDLc-|i=)em%2{c%o3!$J=$F^VX&F0tC zt1D+lwy*A`8F7i&dgs71HToO_@)9v+#=?>wCjbp82!LhqUj-_yAje(-gZ(ErDg9n& z3JFuUORn49P(q}gPzL|K!rKLI5Jc&y{P3B2N7@_c27Dfc9|;KZYtg%10n+fwIC)YAC85Cf}fC^j*n&;RJZf8 zfDa^2Jds{SSc7}!fNj$`A82}-87U=I?T|mXpDYH%iayE#WV1C)3Shxsocnz8wZVCp z5gXmJ0Kmc5zdcaw3d;9jpKSyUUDg{jtuN^A5Q)`pBeI5(MV^`)(;m{14Fg=Z0k_vp zz|D*-6Ad$Sv&VAW-{)*>Du4P;QK=-$F;hL9_+=9Ay?E^+_bUWjCJXwBh0cyLJ<>tz zJ0&!y5#6yGjJJh?=!K>V?`I~Des%~cLKD|?uL;MUPTG{Scg2G^w*-$gS3Psj%wu`| zeQf3{H=(hba(vA%`i$bB33WJ5gTtFm1{A#Z()SN541d1o)&mh!k{PH2ly}al9%iid z2$tp9D){DVtR_s3{=YYE+%WmPMmCFve zb&8ad25Xauz^t{TBm)8ME!6opm2JLIdb0s(wCDTFq1>V|wk~O;1E3MWM2G2pcVhl= zAxTAG*X(Gf&|C8L{UWd;e*>v&XpG|sSk80UEjw}Buf9!C-tES|o1ln2U}Dm&d=t$M z{g&4j>AnzWb`Yb41i%)*7a>l*`+Bt_ADEhfZr!Tey}R&%1_jZ7VrG0;QWDAG2T!p?oBTm^!r#6%613;v?q)p zKJJlngxAPO=b2!zH2}n#|4jm0h5f6YHA}HZ38z`elJmzBKOScXk~hG*N3ILGV_4Zp!^F!E0<~+ zW>dd-{5%_yd5I!mGk@b~tb~unnk*$US00ti*3br+sf(rUV3-6xz*D{~&cN-w)cF03 zX-w8amDk`*mlyG+D!O7YD5D?V64>V-Hx6p|yvEPb5RtuK^A^*0?D6DekZkk3Uc>eN zb3G-dtArz7aH+Yy{tAjD13jowzE|jN$Qq2*mJ65ffhWl7;~jY}MwjS5Z#y=a(FV*7 zH7ge!M+6z--F}TlqefuOyP#D#Jlh|Rrun$v=`Ta09;r%556^?skvspK)qP04MWUe)=4QJO@=R04mm$Jy-vU9pgk zfFese6t?Vn3dWJu@F-BADpdND^mFZ6-vXOr(~0AcRJy4kE1NMP-fu~2)pK9wTc#6< zI0hK;rNU$ZgUP8FAIPC77B|X4Hjr=GgHo)4#g(Go6yn@NII6NiX}9DKYtk6C5hVc zsUj3M^tq%x0z!;|L(~4}u_Y<)WPq^S)+fX;mvm`b%6@r1f8|+ z7YoyNF8xx6pT7$R!>@f2pF?6{L!xa+D0`(%)q9t*1LU)QNDTNU0qc7?upg1lx`mIA zKWeslGpA~<9>`;olW)%L4yQuVm-aTxdDNBH?=2OWQdQ}SynAO4htr{^`-Ev~ z{_$JrP$ZA=B#)OJo({;>)0%~1F#Dpuk4Tf=_bDzJ%{2CP_s>Iys0-9bst|uc#!bt(@d-xg%Nf_Os`0QB@;XZfdhs=~-;3Fpy zr9}&04Vwn@VV-xr1}H%s9Sj|NT7%`;vT{?`!a$uD*UwHEBo}CsxgvLqg>YXVY``EN zF*YT8Y{X-e@xRLlwQN{WIL=cvp9H*%+wv*#M6ReTKp8w7Lq@t|LC%WAf8jMhG?x$QPoO3 z&N8kuNW!}(Z~&sS&hq}#$5PKt9#N!Ufbwb^8H^S+pZ8gsOfG2q;S!O{)6;OLo9AUi zoLl^x1TK%E)|7`YARtT8)W4|?M!;X5-;T|sG7t9q0NASsOgw8M2O>-J-lFZd19IGy zYk#k_LIVol=~-a6lS>T1HBNNg2+)4r$TER(Ue1^D{xLCM>l!-@s7>w)@@0J{Fn0^t zl(GeBC5|KOIII_NM>QtynW+{Y45sbX+ zh~_yM!o4e4KTng_PjxRkkL?JJOmC5L$vI!rdxVYm%Rx6^5pcJw_ddCdSxaq+_ zZliWtek`eYN-UQ&r3d@|aAPHAgG3qMFIzBkuvUG*CmKrLxCW-rtyHaN#UM@#%A3@F zKqCI)^FfBxtbZkZbQFcsXZk28&+?<{gkoGIB0fb)FUeJRr+)T29Ytq6UI{k)b`Okn zmQ80yNq*tp*Is1xfqtYYLW$^^$z{;yjU4QPw!i^m_{!UyZndv43@mmyT%Tqjx8cpO zBl!0P=Xs|WbkI@alw?TUck>I65(X!KFgW905KF;F*ui*Fsxy<*^EM*1&KE#%vPC+Y zNkZMa7#0BzT)gJ0lp}jj4>3cBXTY5Ebn1rqC}8YO>uV_N3<*FRCQc$qGd2F`_Q%Z- z|C;c@bSuJgi2TJd$qJI4t_lI4o8sXQV0z!o48c=dt!+Zs#Yk(#TOYI+fojG?5u|dF z=+x_&-dj)HC*u6vPzb)1rX~Wdd@h z@ZvN#YW&}zy@7zCKVeo`4qI%aJ)MW)nWF(T1#8oiCC(I|DH4t-zIu9^pGYwYI-0KZ zO9UZI&lZpIk1yp&Jt4Gjv2V_F+=N3opyhUZ-2zrAJhg)(Y8^>AD4~}EGznPeyuM`j zUip6ril=jt+a3$BiQnJP$z%}v(O^BNsYE3%$susPO*h7NQp(xW08N8I$5qRNC^3LR zw)hMy{U=?j^;ZU6mezxyuW*#9e5;qzUI(HTGtH&6Q_o$1cKByKJt}FExBeGr%Z<8n z`MD^IP^B6ecCmF5XAXK(FOR{}KKYv$Lx>ZaRgY=S zCJ-vq&ly;IrRpDJJH3vpUNNTbT1ETEU$@Cb8SBP&x1ys1>0C&_QQTN5o29_Wu7mS)w@c}OyMaa@>PE}_Y&HK; z01FdROw^x%(T|rV(SCUDd$J!95%R|7A?O4UWNS8fx4eH54DU08*SRi?75q4ikKfrF z`c`FoSmpq6k2C!jJdr9S+i9?z^M})7|GCCD_)<5-^3kH4ax*)*Nk<|pmmd^3d$<6k z8QPFK3cECY*QUi+J?Bu*b|Fn^mBmAQ`df3Ux zCt9UDyTfC}O5e%6t1fUlE6*?cf^{>{MiO30=&XssW zs-$w@i~C&vdT-^vOZ6ppzrjZwZU?eh0DUudIyHw!uBVFX&Wy)kxXhhO$NY%$Y&XPo zvu{^_z%JTlKJc@lv_xrKpJejz6B1|M&sswPxkDh0Vs~yChmNFI_8xI!4?qEEFt&On z<|16Ipf^91q@bRX-#Ok9*h}`INg{-C@^qF2EXiDTQp)cjE_-2-*y13D1rVuRLqYe> zdx&`7;rc*JNhYKqS1u zgp-xW=+%b|9!CuLjbv@j)R40qGCVxo%JNrBBwpgfoteJ;65#$ZSDH)X{s{8vpoF@- zS>gKJYAmJ|doA;vK;N_%)QanV-c6-&gTtS^G+_U*a2qsuiMBs7z;{GJzmp;Q3`ZpF zQ5!EmT;+>?^=KD@#-aA`u-$l70=uA7O=e66Zzn{j;5f{7g2A5`*8mMk5Zkg6Ci#uI zc|gwoRiZ0Ied#7V+YYb{8MfY%)~4FKD73?@C*`lo^utJZ^kWo__XhIiI>sjB%q;1} zs^?DuY%w{cN=x4@gqd}qUTQl8V8^@3h4#cHpjbq<^49ntFKKa}{;u)@G(kAF188!f zg(-KNY7=>CVd-7t$B1kZj+*a9pDOj!#r!&%Yjs<{d@769=IJ*iEBlxpd=xy}Fsax< z?ceUPk?uMMycqksB<#r3L9__1>mg7O4}o zN(qQuk%&BsClG@foO%REY&Xa~cP`)lQ0&SH(nnwb$wt4W)Ujs%7Ca}o8IC*xq>iyV z;Br@f|DIv)_~ceW9-G#1y2WQ?+{z=?9{{V;Nglo}at~3YgBqC)3_gTKJ)BS@d7fW1sgTZ{5Tb<_xz$TN_Gy^| z3U|#-925;J`4I{i-bu;zenIoSc z7iyA65Z>goVq`vCOP|Yjpts+zJ@I$-?Vh4;QL~ONaQ}-|b&8Agrw|+IIlXwY?@`1u z@)pE1*qC(2G&%ysmm*98kldJ7bo+fOjQxACY4 zY`$G8Y?`cC69SlgfrD#pj7OR40bcvbv()WtMFu`*q)eYKBxe*1G-|{8;#i0z*-rH( zsR{IC<9x3=rk`DZ_yA`@b9%UwSeSiGSHS}lY*O1gH*b+N`fl8v9z^m4JH#KRDBQh^ zQG`teQehae*vj#UC(O|GA%J;sYV&7r*^@M5j5@&S29zuk)p(mk)45)g@2kF5;=8eE z8kg|r^VuHeTVoEAa7?5*o0^HupH1WhF4$N;{LFhmb0Bgl)rRcl5-2btEoE?7+S@K* zh>`4fl29O*c?-H-UiS>eG}*0Ck{k4{{VEe`_)S6IybdZ|Y^{iZnJreI(x`b(9`7mI zZV*cy8>YX>4~B<$xaKQmb{O+zY1pydd*#74=rdbd7zAooxyF1}EqpIqG9Ma(n# z#JCUU3R(^Q=(-2+sUZi>`N;QV%U^Aq?*Rq%<(XhueMHc)OE;c(aiM-6_zURlfUfRv z<8Z~ubf#*(86wO2MHhf~cWGLKLfQHg<+OX8K{$br|#a(U3N9w3w-0#Ww)FAu60T`sh=~d(}qTVFa0>G zLuX=um|f8Dp;x9Zv^RCP50hZ}`Cg3KL3O(8!UEOfa2>e86^qIR@k%_OwVy+m&q49B z^%9dQS@Cq{n3~xC&9DUc2KbBi^_erwxfNvzRC#|3Ei`w_PKXh(8TzKSAb~TV(gn59 z=t3~MeJyOTh}8P=ljNKC?>sacpV4p_1coj%C<#7VgE;g%@AzEJHrdIN#k+07Q!!DL z$>Z16IcIX`~yWmVo@C?H-am3_f6kUpr4PxFShJ9D?*hsj4L-8pFDVJ)Z4_T z;Kp16eOFPO9qdFmx?5=&7OCCY!biXqK4G1^!L;ghtge)wJCPzKO+z`|xiX%AIII~l zRnHk(!xvLwCzI|5W%RFthf8LM?g*4FSbU*$EV z4b+SW1*UPz+7JqS|3?g*yulnJCm*6A^(U5{(8|aNL%PY!MulONFmCogz1SINS_T0k z0!_ZQLq*rzYmJscSZZQytFWtzJFt8}lV=zw-Wz9uev&+TC| zX<(4(eh8=;Guj1p#xx6BW;rq5JKgjC4m4M9}XX> zYN|2;YM5UiN42UgmJE_)a=Ovt@RdM6iuVBfaH+OGh4T!0q7V*jucy;{v;12`a_0Dm zAAalGG4N~CMyEqRhR9ex8wJ87Q7DmJx+CMbH$*jY#KzyxDzX6q z{3dBR>(ZLbGzFN?@{#LAEx!H!#-NU3IhBJByF+Z~X+FR3K8{eo3G}B;672y{C5?3* z6pW#~Jalyx)PSzVtp3p39E4crZ1vP?H+^E>Lxgyr`-!{kbxKueRbjD&V<3E1oI;kE zt>wGNQ5Ejd-N+`Qwr$;6`etqJj@PhJPi#Rlx?tQllMq~S2b*%-@6L3ad1ELM&Z9h>5=eU~);e({Lh zffS|R|AeAZScs;J3*W%*6i$*m8sA4llk^h``Mx7G;s9q!GeyM}C}pO21qS-4@n#vu zdccW|>`Y&{9=}N;cJFTCYk0}XCF{FP`mMfQ;vy}y-KR*Q){SD`XD#3roNIp?=3zzM z;^<8!dUSSAyn%FbDM8LJrW%~@Lvm?MOWXT~;~^kgF6lcdqy@BgfQY`iq)o2+yHhT# z3R#UD%a~tF_CG)-0b)5tkcjNi~8E9}QyhvTm%$<71vWrR)PhAvY8ao0UK zj$~R!gbt4TonW9MCSLI#Dw(vQ`Cd!`Wq3*Vo+Wt^!Ly;{8YXHWY1^u zLXCSfe#VugtEZofw4*G#b-D#PtZwNPuI=3LwGw<7SIxGlruhWq=got{q9j{m9nG|> z-^axXA0-X`P@UqfID-t4avN{u+jYk-U%1<$60k~-3a)X`P&|?C56M+~fv~>6E5)}S zUdbwW$hc1OBA1F3 zrJ6n3Cn1?Ek?jH9P=l4)WOCU#9X?vv z>MGaa=V>fa+bKpRR3NuE_}oRL9XEFz(_W8$nXfjCzZ}VR5SeFo`gyMm2M5iW+LLKO zcj)DtiuN^}e&duAO&!Lr_3F#zY{NOwBCyU6@C-cTPUZ8ZtO~ zJ0`t)0ZXd~v|PJ1zHWVU zE+Svjhh@$C1t8Dt59wTg5yg2t)c%%6SwXECf?{!*Tq2RqmzMAQvt-`555oP-ntDCp z8(jQ>s1LcU3!&fN-T!U+=gc^EMp3znQxG5PY2`G?za%2U%b|(V|<6QIv?-e1WPbjIWnZsw6AF>alTH zS*@-xfed9Id>ue-;J}qu$@>zCs%wAfXYe-rGd$uym#Y6D$P|ol z;>O9jt-{H_6#P9}`B}NYuiHj=lG;rC#?qlNuhS?*V6LW;fHz6Ja#R0Bk>WQdGEkJ<9uxlbJm64ua`4VR(cKn9X+eR>N;K&{jI*6wEl1VYHSK9 z;*&H~jh&NX9cN@W_vIhjYFXTJ#at{DjXob^l2Iij@&;J19C*DBk*O2*zNJMdcamwi^ zH}UZ`hvNYrFKpK-d1kp~x&y+mk2%zA^M9x1-w4x2{d*`|yeyu8M$Y1aM%@cSESHOA zq$Stj&)~=l4<4UwPk-iQ`JJN+-v5sj7J@VLrB|c{isacB+CKi_U0zdrR3ZHDcc^Ih z*v%tUStL(%TLopEYoMsU2blkF|8nVY|5|OzrYr_S@fng8A~gXP#GbBN_P-xkV_UBE z%IW1zqF^b{{&AYtiFD!O|8(m8dz0WrdRLq*lhb1KhINp|hYZ#9{OIUt*eo_ajiBF? zum9T9gQ8kPk`XT*l9KTM%vOM47l8TxK6dFFYJ%H0;k*MA_>aVYc=yyYD!9mWV8yqW z8p{qN;aq%g1fAhnr2f}{QJ}zSr)M4bAROn?5036KO2!DN#fX`1jJ62CN9|9rXOs}N ziW^z~&+iKu=m2TBUnP3rR(q>A+s)xQjtOa^nH9aZZ;BNP1BXVM*{U+#@;>VP&;X}( z{rg|1m$5HohvyBnDh#h>DJqHWU*4Ruv%?LJT8;2d2tmi*k~+v@-1j9f9dQcLvH!;k8~$%NSdSeV4%0e!rly1_gmHtkv~ z&Lc3H!sA4tgs2*3YyTc9#!U9#36K{;{b3N!0@J}7tsakho27$88DMxLO_G1S+Xm@R z*7z!<=QW!Kt}XK%C|KVfd_}y4&5o(5xG^>JBO~PqL!;~^#o?;H67C$1qr>PDhOIwy%1kP_$r zW%}!g6;)t&3RBdYOb zY0`L;!gy^6bK5`vSiD}wh!5wA6cR?sDtyJGo3G3+MF9iXeLw2S+tyeRtK1BeNFdQU zL60iimfSVCgJSP0JKW=NnZYbbK*&Lm@Fqv$^1bACKzvjjgho2Lnl~ls-=V0D!}3q@ z;d3xGrd}3>S&g)IN6I@lMV+=wwkMvppf|#WDemamhW60KPmYRCi~7Jp*wblBAe`CF zV?j14LT-R9YU!?q>0?5^v@baEQrpdBsP}jIl{4ZH^(D z9uD^u{lS>K)LH@c8tV2+)uka%Eu&8`HudjLVC4*VSEt;Xa77okgaT|h4?7*7bQ<}2 z$|Pq_p2{z=i9EHlD9bA_!?h2-E5RimgPmj-+@KQ9X$L=Gsi*s(&y6kAU{|ObN$S)eQ50P6ORQJt-LI zAO~DJv9@2Cqu~xDqdi$$^cK<$PI{r#a3__9BlIQ=;VHZqrePl8&!MV_?r3CHTcD?= zWOh!X@^4o3Hd>gSCDrP=Ro6=Oi0QW4#DbQs-LTR#BYz9iSHo7zR)k|y{l%+2TX5G6kI>|b9eSKvf9-gePqImd& zhfv{ipYR+6`e^Y16`W_BQW$+L9tVPBn&mGeAxxJl4CwJgquJTeCr_t_KbE;BA?<6* zdOr*WxVIgvr`qTPBXRU;3&)V#>n{A(L&%NXj`C&2>avMp{_~nAu%NBRm^^&O+zxuX zJDr~2>$LCTEH~Ql;cN}MNN4>aA+YAHUKutMC7K{nQaFDZvg7vp%YKX;!~~Y?qazxt8^SZ6ioqI6MOvXxL8f=DC z9(T6mf);MGsOw`QBT>9A!IlSqn)lQ_Jv}+cepN;e(!jp;xdyCmgEwIhUO`Vx<$h@w zfUb%5i#w9IO)RpVVk6Jje&S>5?X2I~476Y43tvk*qqrPWu#zW*gMx9Lmi`Daiea(z zzs7uf%3Eyx&$8q49%`Nr@2wlo&{m~Pwb?9c>YaAD6Bot#?yk8{g4@jP8+<&DV@s`% zIvH3gahHOQhgP>d_NKD5JdKTKw{7@1#>qrDJ-zoFFwU8c5`$68JFq2J2=hY9@aHr7i0VPqVpgX+6lMP44oH6tS0d^4@ zcn9^1{i@wN*52MEII&Nr4k$$(!JO*J_(Qc6wU%tG3{H3LF?flBim29SNEb2GI4gUs zmT$l}IrrzZ`}^yZEq;kKlRZqG%hX{?dLLi9#?SZkg>M<@ov~dH0dRSr;ZeEiyRzQE+uFb)F>0T`lR^?s5Ax(hUGlfHPGP*+rQ&BGvw9%{N3Pt zt=UAnE`2KGnrAQK_3`P>u4qvq$C$zZ7Xy_)Dhf3?{Cur3tjTss$=m(B#Qsm4`hK;3 zxm~+SI<5v6F=~=a{M+Qhva&J*58SK9apCUtf_x7_YZTL56oQzz3)i`#QKodUl2C0} zBFarAO{F=o=fsvN9#gz!aT=2BF4aK9`KKD)0qwTo$Wd{7NCtzV#)KvKKSy(JJQ#N4 zfZiqI)XeW?l~)5TI>TRvzkesnc)nrw+IjJ|p1+=&ExA3W>0%hll`i#r(GvtWaFuJF z+iUbZE$p`4QjRh+FytkZSC!C{d#B}q?{T>IcL|C+CbQdTXVgn)tSxh;=2V2!1-~AO zCnw*Ict)pPk7{D|6q(U8NoS~1RpA_c-#0$oh~q=|^y~p_e`Uwten?Wy{|)5poDk$BcX^++WZSnM z8*)2-@LO}D^_~z2m*;S}C~4FF*9$;FX%yaztPat zxD-(G?eX8jaH`D};;sr;ALA7dS07HpO}#Lycw@B5#-T49_K^M!(Rsqa+#IjLNa2{dMHr zi-0)TY2Is~*(+v=dF3GV?Wyqh8=gO`$+nf4CSs#Qa zK<14ZV~E89>c2WQk`$?za&}`9o$CvIy{0+w^zz(8ZE!wxX0+2)pvDjcKn70)<(E}8 z6VaX0Da+H_0!;iFt~jyx=Uvwrc7HlT9x=(}!c;`FR79WAj(o-0C{J}_Q!;-itO&oy ztRLOrQh`$#^9zLM3vjrlOlk@r7>h`2wp+RT4hXVLmOFeew~CY6+`G|3|BP>E)3dTE zX)jXBscccYTl{y);)BbZfFnR*5OR4_|NZ;N*{(kCV1geEh<9kJGN8O&X1lr2HH$3- ztN^V9wO#^ z^RnWgo(qeyA*u1cK|qdUW(yv5$<|WfmYh3&!k=G33N5J>-S%9oMo;l8BR|=e7)%PaYVKJ zt0CP!w)JEH?X;csjj9|!oF!t%^+S;G!~PdogDBnz_x66AOX+y)@ouNOc%@^{4&=K>qc0T11PhqkT%oi%omI30og)+1s^k zf1sDv!3cUJ#QpliGINnvc84?afIx;9q3RS!%$MGTQhhSP{C_%bIrSh(i% zPtDFi!9bov5wANlAL1PGZc)f@0>ffM$OVF+sOt+j}cj!#FhwRo^Jp>@m4hOxjEssjFo>X+P+K)Y54OKVq z4ZXT(GG9A(IS3FsD%7^B{)evW={2WCleDRCTE~7dTOmTyxG02v+m~|D1=c|%&5o1V)Xy#9A3(Vln2+UKiFgWOIvxUOJE7$vV z=>sZ9vsI{L@Qi&lVv9o^WpCT-iYUeV)w*SCt9FKLQ|gWX2oec_gx$=+-@f$)o1f=6 zQ=Zngk`4BgIGfZJy4}i1f~F;F03{Er&?fWavTB!Xs_!Au^4%f+W8V}gCmvzPZ*GvK z!S>Q5!{?Wpt>BiNfRb7Klz+faaMd8gO;^{W!*Yicb&QD#h9__Jx_B4hX?L)zWjV{m zHdWXs$``Tjb3wLg{^{n#H0}rCD3o9%vNz?JTEvuH9Wj3yn{uXTHmjfwg0D>4y=V)-&>}l`Ms7tsld?3w#wWu6ht( zgwyQgj)0alWw0uAgId;i7o-K*$6#`X6__B`k-`l8u@tgYe-Sh)&|}<~NLr8onOR$4s`~e0*GOu;zBWF!B@% zZ150r-X!RH=ZlmS{$*`lonG!;1ygCzT*vo{@JIBwa&e+hZ(@vMeDDkvMY9NI3*wCJ z{LgTyBMKiRgwD5{VkK=)F<0$iQl%BiQ0MqtNxA6urc)|b>MaTWe4P@xiE#k&op&cl zmH8rDd|Pdo8E!d^aTQ)I=OA!mF##|CH_s6Z9Y(o_mSic+8tJ@KZCp3wQ&5ori2iWunRg zIkNaIxY??AvlSOR;4n}b_#9TpQ6*D9z6QXSlQU5mXR-*>g^(asn0#Du=!PQd=Uw5+ z^80kbOQ~|8w?)L8e7IVSqnr)m^u3QVEH?GXr#~6X$3uj(5nX#{!CIQ_M21c%s<);< zK})B-uMdG=8z@ehLM=jjC_E<-Bt(_@(XN=K+e1D^DC%=AlEHoXZpo0$PJ1Fs&-?ap zpsp80vS81_^r$1xaI(EH9R@7CmIjUi4V24tD5*uM(g}7+`}q)_d~bwg=rKHxhYZ6K z={-)mJ1 zVB*Y{Dk~=Mm>rCrm^*ud6X{~vht7i&L-gOa8_P|0L$~7k*?V^@Vi4?=JVV94_tA+} z5F)Cd8M3&t8&FTjJQefo(E1E_zeK$6-+aU%yg+oJ_#tHJl9|v^5wVAKDJIl-o$ltYYh zfp*|2H`76V$L3P`_$PS z`SLt0rP0)`Dvhb&Gq;!5MITe*- zifMP@!_O92Km#4H+;lE&--#?S9iWGkCpek^X=5Mqd*T0ITymdQX(;t3UjeWm?j96bnu#^2twR{T;4vY`3~&OTH~DmNHEJ`m^-L*TP?pbobb{=s4f{xb7Zo-A$b8RAG9z{bEdy zL;dw=vJsh|#cYtL*6mvFiwiCF&juNXCjcwcin19vql-Fk{AJNb|+sk zj_zziQH}o&2UUR2Zc(mgJY(Q?l@(6UT#`Bds(F8+FofLa9UY^Pz+`}J?`*Yi=3BDK zGatm2Y__5`-^U9Kx}e>$^v?0>{u4jSq@AH5Kd~zc_Bw{d{v5U~e`$ zH${zhO4U8ZmvjA&7~0NIQ|DA2z#^n#rtfX3c88|^4^v+m7gf7{O{Xv*0+ItFEv0lL zBGMqz-5@bE3^51@sI+v0ibzOHGjw-%$Ivx21H9YkJm>u1uk&GkduCtv73*5}TIaO$ zCBLwrBXJIWiPrbYYBivnv0S2OV;f(|l{Dpn%W~gi8_a9Hc)TiD+|9{p1+4wG}!JX&7C*t zJ9EGUo=ip58SnN0Aw?efzEwEm^~MLxDzm-05^#oFi`>`M=%y>?03&xgk7c91z)K-D34WK`gm4R64o|~mp;a2 zH!o|{w}BNZ5^(#lekuEzhp@l>fnn|-wVNNu;k|w?^Hf=~G)%PWC2H&tro?&4BrAkkO+vMNYZ2GLU{_PUC>6Ty ztFRsmFhnt5A4;L32I0#uozyPQ(XC#u3a4#)f^#)_j_07 z@?ETkVJExEnT36!wRYSxB~$&xvSbypC?9B$k4_IeEhQ=JO9p=pp?!bx>SeO6AFXw8@P zz{FAxeh+Q#;tre6qCLnELX2KCdC)c9&8C^p5x=aDY%;z&x{V$U@Y{I**=;byB%3Kp zG*qF{uv}0mTGIQ1?#^|~^7>)Gr>$-mCc6dJ-HwAf))8(`pQdHn(v;4i&ayDtC%(aL z@30%l&AKTqJ;%2I3F(7{%Ghf8y%xXRPx$QJ&suX_?k%~_pP6)cBA8~X>g(63sQ8Eq zf>7_FuplE>(Ti;sYf0wZ>D00>b47`Bm7(`L?Nzs*ETYZg(m*o}R*(p`A(#^j;Kg-Y z879T{5uTl_KnIoCD$elTs*+lGg*5Svg!NlL!;O?suTmX{tucM!pI&vjONX9HIE|Iu zU5dH4SS2CbbKWdId8Os040a~$F>IyGmP@ZV{Ur^^6x4Xtkp7@6Zz^m{d7*vQIC>H2 zaQOfNSn1iV{ZylQv&E5jX3Ovm`+v$v12}=Je#zihtg29|fU~fSuLbU|IDsGA4p>_8 zb?JZLc6B}J9Y_-#ZLj$o*2Cv7y_sZ*yQg)9RzqUGGNU%t0e=R=p$5#ff=WhjP&IpK-=?`S|nJl~lA@ zf;;thbk%@?;#mUuO#^qc3IhqH=K(qSgz|7-t?IuJ{D7_C$gjo##2fJnjC6xQJny(ipU_)6S0)b?ANRwh$Qw4+ng({4g|P;SCvg zdD3SLpudGI84avvli|e} zY2jE-LUgC$XzL4GBbrLVx9fL-PP;g^{%C_jPA7^~xFdQ#yK6mYM?^_{+2qy*Y~h^2 zw>KkUTN5)cEPA5Fjx9xRP=wo0@2Ksn4>CgH?V-~zkFxnJX5Lw*$D9C0-MwZvXcirh_{Esvr&@8V;ZIxMb^3FZ3O3Y(Ls~C6 z*Sh|1!`=p2_|3&}pF*N&%0jpQ80uHxH)S1}&nLNm3pR;Us$* zDKTIM`rS@ipK8juwXHn;GgCMNh6-OQ%{ZnI)U)gRSi%#qQ4u^XP%lxuwyi!$UgX%c zw_`8NBNWvJn%g3x7LBL(hY6arJour0-lZ*~!7WJ&8W530z&Zq`$|?MUlMPK-Ns~Xj z?@g;Zd_Trk6j+cEjq&lLxQn&1`Mljdyut+7f(phaM=}JEj0-v{ZY|3KQ~XVzgCLTd zY@f;Q>MJvf{aT-TaCfEdVvobO)&lIdM}g^=y$$Af__dttiD-V}i?}47_3O%;Gq2hS zqKjM7_}(=vUu|L8EeP%0%p63`+!W_fl$6R4YpGjenmeGd^$5H~F? zrog9+N|#p{aC@>zBAf-WWn6CAW28FYioi=`^W$!0)O&^ehfnyIGnPt)!R%nH-VIJ! zx&L-HDA!=lGV)k#A^9=&il*g*x7zR4`KLS>#R!>y=o;v?-b8?w)ha;rqS7Z}xdd;- zJ?&iD_3c7A@yM{LzFPT3^<`%WIBHeVa*p#6m%PuoEK;)tGr9CYqj8Nh<&=stoVNEhiqh(?b|Z`x6U) zF!npAs=FN$8k=P<{3RQ$>ORb9u|0bT3-7spkjZxXs6n0Q$C&t|7hyK!X`9d197#cO z3Xk~rsX_9U%scu6C7e+bcUYMuM3x`Wp}XsnIW1ebZTUG;*a|R}E{6=W5lX zv0bWW=Fx`b?&&7%FWi=A-x_@j(z7RR(rL#%BZS)ZJ4A_)x@GSVm6~H;bm=HC8~Af^(6_6X*v>sA~7r>9nwWLMd(=s6d-#Pp%6L4FpV4s*( zHH9Y+bbRkTs(Z)qc{-XFdSx}KMB>d5^#eW8uh{Nw$I!S5@=(edZGUqgL(2QmXlJ5c zh{y^_$`%_)N+C?|x|MS(YClmH*Y|@d%CsWMQ_nuM?fFZb>3$IH^Y}a0sfD1XJ-Q)M zQk((FPe*)XKZniaVNoiMf)!PMNjJ8S=HOmi1NBtXx8i=U(e1Kk&Qc6*HxIWrilt!6Ftdtc!wwxN(avK}7BHwPD_` zsW+4Y(?5x{xAqrg1`CMS`$U7!Jz=)b|3AitbT${rV?$XwL`~2lRt9xcU6P)d+vVPy zPy@&%`6@}Fnl(_zZJy(~N|buIxGy8h`S`ZF*S(&Qok%ojhBtYbi=O1s;C|@~YJIrU z#NfQcfLSc#Q>I$7^Z~B=Vn9Rp6|^t#$emA)o=DUkchJBJO(xn7Q1Zkw3GU9E&;JH! zGgt2G7O~;mkGh`MvPzqE)wBz9TZGtYyyIpy=Uz2)^H}N`pg37`cIQ1My4Ync{T~S- z8_M({=eI;t;!>w&uOU(%?dc;30S<`fslg)-_ONxuL9^M>#;_L|hM{&gW_7(Av0Ok;Hx6lg41U%?`ou9I9#M9}-8HK3 zgquTZmr!_3yzyjwVY(|-Zcoa2?sYh#6gxQD&|eP*dsYSiyDSfvL9_hM1&B;qiETXa z)(p=#v48YPaNn7s9^U3bWIs6b6mFahhM1&>SM>6`4 zmNbm{F@jWEh_V8f_*AtVlWK-FhL`0BVGA=0b(!LyP@D?Uf5UoD2hV+Z3fZAN=+Qq{ z!m)(GnI`@7<%!TEiZAbz?4EqM0cNW64X%X_+oE?*{kqmt@81Q}=kO^V40n7aNM(5D z+f8Eva$A?cEB*KBKQIozB>50pL^Vvhf0tK314$*C6)sZc;QGzOY6wp!U{keUHe9NW;ItiQRI5`zrCq)Le-nW| z2FB$R+lcfVOfMypMN59HC$G>NKw_np)b%H5G-d@g)fTJ-)Z%B+A|hzr87lUhwN@K_ z1f(4;nixTPa?3=uX0gPSSh$Ryz?R}adawHQ7z_v_rIYjlr~`=u^o;j2QJ@3KF!xfK zaLGih>vGPSrbFpx{35A`w=K5NrGUssEqwA(Lm2t{1j$0Q#IC8fO2i|_Hvh-$;KFMz z$|X$uQ)yK{JAE{z&tY@Mi}jWPt+#7weifd@>Mtc%slCkgF>!Zgzq?guaH>o{xoZ8* zPcrA;o>wLvD&2gZq~BI*+QHSLeSm%>bZW2s%1mwXT9V<>Yw1Jt&Eh}|>R0nu7({oP*dD@miBmvoBz+^WL4?0d zIDpU}2>q&?{9iD5_$x`XB%-7L-T8AFp0&oot0rL@r2bpZ)FLyiA2hhAiufCEG3^9O zx%?-@l+zK#S_wt(DCN&I`9ezNz2gvX`t=@WxfjNlSuo^}e;OL8dz-I|^&hFkzjaTa z2;=d?x#RBHP)9?K_dCBL6nw~OvUhkteZgU}D9o?W`fT)XI85r2RE}8h>1l#S0|1R%gsY=X^_N9Hh)M)gL&g`KxLFM?C>9jVxot|3C!$r7UM=Z!PcdPo&} z^g*D1Fq-@NH{VXRY(wqdKH8+Sp@y3JUq3ivaO4-CYQ3p_-S=jgR$!A&3BNkA@S|ag zo@k1(bI8XECUH)^HLBT^|AzU$aWXuPhsOybz#r6VG1M{8iAl;*1MYZA#95qfHl3rY zQ}3OYo1Wicu4qLHWC!Q^tNa&q!5QGnUEpFg!1S;kL7 zt4_3K3LH)U986Zv{=HLWjaSoxjcG0ksf@5dhg2Fi({Ldwe)gvS^NUqN(KMe=M16l? zgWk%Dj_7!?6*rIoZED=vnXQllG|rGik_Mu;>zbih&$0P}3;BtGpl4T+L{Mc( zj33Ls;Qg4?%Mb-bFp#QAgTb&o}oLbkw(sa5|)`vSN+}W4F)DH zDIb1L^y`gh=l4B#FwEO`eJh0Js$f=d9QnCqXh`~zVP_5W+1pvys_9P*D^^wsl{RYF zfE=!dfaFytabzS;lz?ma?;QMpba0#DGSXQoPNnNSwkOC(Lt5e{$LSr&$^ZN#8jR|F z?Gt%L%XJ4}#a~Ri;t#;%;RkmJBYuw^_qwx^fLt}Hg&=8#$?x*fBs8zp+qhXHYx_j? zQcN>GW3Rn!5cY}4W_@j^r869(^X?t8d8I0P96eV{Yn0<(gPl(wN(KrFT!-dbTQLAZ zP=2Con`v(CO2%Po5|YJU5S5m=eTHr>b$XqJmKD|6){76F^zrh5Q%&x-)+ZnBpQxZD_!IA2 z|NVZ~auhc=&5?~Rx(TaW;lw)m@!2z;5_5x-_e|>prT)rKfjnY+i&x`D-Il)|l8lPGkF z$Yy}jA*qBVE=q~;bO|D^tB#Xm9|siGsQJv*lg)Sa_Y*-bN!&OVJ;wiiQ;uXVd9Sc@QoZVzz z{tZlAqHlvnq80GjD=SV9AW43vq7;0Ck&hitFwqEhg=tG|PcqKXmwLkjin#ukD z1pT-ynk;eV(Cfin8H>N-^}}o!iq>>3Z3z`O>H$eIb5)M{Zh3>{moJL-ApFQ5q(CC+ zl_l$N+ZacVb<^%;u#gwzdas&+kmL?Y7XLO7*;h|KAow$G z@&96LtKq_zP~|Aw(b4deSw1n1jBiRY-?YZ_MTn(&hs7TXO^Roy{7CYBY)}4wuCQG_ z$+qVD-#2k#NgH8a7o;8uhi!alc3=jZFm1|Z3BN~Go~S)!XEi@~@NV@Z&6SuVR=_&r z2o1Wq`$0onpu$u8P2=f@Bm3~ter$o`e={={dPI)5)7q8eKaU+)NkVd6AnK|bdo;;) zd-JzN&5Ig*2QGlD^EKi{x+xtU3D7LQmRnsql4^*J~Xa@={^SU4o0P<>no|v6ye_ z3(I+)X%{_4VXU+Wp?@s7Y$l}lTJX@B_CpS|0iM-wX(VZB;{4UFTW?%u$eLWUo4=1k zNK#oEXRkrGc+FJnM=Tn~@cDC?k1vfs?x8rZ7QN{_9|e_LI_uLx&Q4~i*LPv9%uHjy zEuBr5O(fs>N6&nuG$L>iQ-!`%eNtAxqm*Pa3KrRI#+7SmZ!SXW@Pmpybg>|KNh(=d zr^pI>oG9Z>z1{TSV!PiIzX&$I4;Xg2;*1;+3Iha8{Y_!r*xl64(~&=RL(&G7wi-1Xi3X=HNQuxRc0-K1 z_mXM?KM09bVx0*sdRqu@{~&9t$ntlux!x(<=-CuWNDGId>Z%4Y%{+;uw zqs6Y7rWOAdxE=C%Mk0`t#7(Chyl*Q+b}L*mDHznPq0_g*XEQkOeB9AQpawe`Tp@y7 zDqSghRYSh?EUFcwL!UgHhGvyX+Jrwh z2lWIC+$*#8fQ{9uYgy3wpxEI9c{-0DsIL>ME8oJo-~U+IHO1Iky1%>d(flBhbu%9z zfb;yQ0|(=73Vtrodm*+YqG>f^cey<8)&%CRT{nm3&a?}GhTZd#&203^0`{IYd*ywn z=^_U;h!9NTX&zeF?!c9;@6iuRd>8E7E}HHo==zF&c(q)yWoBkp!ru>y&-@#y{LtpL z=LS^KE%ur*2RY7Uag1h6*qbbz#R;tYf!}h9_ywLJ)N&43?li<7#;Z+W&t8$l5XU8Q z8-3dvo(r*(n+DXi+DvXI#5U|_j*o-_kY3kgrGDfP%={gf;6F<4nR5;mD$`y|{*R7< zU;0J}#LR3&d>1Kn#~9djvv9aoATCog_lC(zyR)R}?)s?lFyr^7n$Kw5qt^zty~c@T zYsJG*(=X!77RO;yC>i0cZ0wb|EL2T#;$h>3n~Oj~+V zI9)NZH28B}ta}H{`R2%P&@Hp@88(ZLf*TL{RFkr^yE3UiFnq=h>1C)6eg1A@|J+aA zJSg$`ih=g&rO0%i_y#-m);D{B+OA-%sq+tM7YgJ<@3-1_&ViR^iCx<9QDR5Uf5-Sq zI5})rsH$P!_pp(==7aZuK$8{y6ZMpPA*m$NcZ+t=&E0*|i6i zYC;Pm3FBg$j|@fPB|Qyfj-KE4vy{(4KNzTa(5>78kN0ZCbJh-Vv;w-f zp9bVG*3;;3&UcR5K!iYQYmB-=4&LZpjwZn$1DYCCM$X1wd;020IV^}Ii8qI9mR!7r zFLM5-()^T@RedJO=8ILU{+Oj#%kqJ#l<}97Qm~=lau@6zkcO4R*z>mpA3$0is~3J+?J5zP zI@7$#+A(_!qTlLN>ZL3J%*47Y0 zE3rumbL3Cgwp#e`G?89o(}L6Gv7MrxyssFkMIzrtFgX4DR=~MUUMQ=ii!b!Cug1x4 z8;IvYbO36tthF;>}@`6!!aWrc!I>TY0JLoYlRWHPzpgh$|!_%mC6T(SVQ$ zp!A$~5gRd+nFitw1GIHKN##w}p6Hnzq37e)O7BTuAaOd3X*G0oXBI7yNvkS(m3wW< z0SPK=o`Y*Si!ENjFW&IhC|qs|HRVA1ui9B9DKfA6Z9W3h91IORT6tf7pL58gx`>8( zpN}s~;<~e%K4rM=)lmhN_-*8ubJ<$@TsTRA@wLAb zDdp6=c(ZuXCp*yepv1d_)1#Q`&1>26R?>p3{ zy?OqTMnc?V8HtB`X+9O}mF22!uT<)|W&qzO`dC6hQ9C<}C2b@!;q@q>&%{7k%u+pcVX0v?C*jK^y?45ti!S!y* z7{lmoCy&{JXWBlG)aoauHo$tNs3pNA|?@=UOsB?)iE!(O6ZLStu#%T>xIhQbA}Ey*iZ2|J+$C*<|iJs5vf zb(jWAyQOS|YzZ$$mu|zXx#@1#>*H?mO=GJ=?znbkSi;IV=l4*|{ppqgoR*8M`z!Vp zgQe?G2jt9iTDN{T<~I_r;AJ)MAa819MD9^YcaQ6`iG?b)W0j9fH7gL~zMdL3$DaYU zu~!o%uw3*@&p8RcGYNm^l@Q9E$T5@qvpEkBpDh1#&HAgV<+LSK-F2|g4-!)YUdUWT z%yF00orXkjjRUnjzbmqPVELO|xChE7rc3a*|AkRDCvppMdYO=heT{kbw6mZjk<*k& zYFTXW44KzM3r<>I2K)67khq_=@!}i5ejy9-JmNyc;%-+8F((BZu7Ahn9Xt~RUVL!# z&(tM`=VIyqymk^0NjF7{L#?FdAe|NTQQzyO-c>Jr$?D$2#GmFl0PiYJlu{DHlbYx6 zGEuP8i+so4FBf{dvin=e#Bc0?3d4Zg5-#7sorNo@U|7>DgV`S(6)Cjnyxg3Qj6IWA)Al*eH+wx+h24Ny5*QG(BG6N~&uH*DP~Sk$lBd`E zR^o_tF)RzxnSWk$YL5hznm9m<8AUBKP=wK=i(0brC2BZogC`{54gm@sQ@bzk7E#sK zRr{F10(%0GGf`R1y+Ye$S(ot*WVeqOT3Z!Q0TJ(qi$LcXYI1jiIN40~3JtiazK^F# z0Hpqd-1Zm&$1C*~H|HPb_=x>!qndr$?n3Tad^7)`e-r0iTXi^^iWsL z1-ggRYpByIP^~{MvVFMC3%Iu7ND^Wbaf;3s!1@r`Pde?|!`zgJJoTx~S$wfqF|_Do z?Ayfm6EB;mKEmzl!eF^HMD#jeHYZyQE@ek;envvimdbjt#NMv+?utHGX-L zQdSYq5`l!C(;o3~SwB(ySPHRtg==JYlOc?0g;*kNx)X=!B*k7awW#BWdHpQSHw-bY z;ZDCLUaAevPTzX^vid#W6|+V7mPAV^PsHQo3yzi@k>R&iEO6J+{ak9}WBIrs)uZsi zcYXdxEx7IiNm_NV=SSd{_S=i4sb4{3KE_XK5AnTmj2weP6ACgsXW7NqsN_QcaXR_B zp>%Q=jF*={uO##7U*|lX+)eIQAezgL21%`SVFg-(d!t%PiHw674(Jcm|57QjW zb+vFT^!oj(G_2QIK;V`JVzcXz3_^xOQA{JptOt!a`V)wXw&9R{8H$ zqTWrntQ)_}I4Kdiv-Q=1#PqdjDebCj2j;+anU!6Wdwo*~-_7!^N5X;7BF}vYj~`#U z_o@Q*5$U_Q(u-cslJxk9Dm09gBcmGUSgb!yak%CeIy7qH$GOz*mjo+gnV!j)#}EPD zrp?qs4+A{^)&i#Ovf&KV8U|H#YdnzsyppK5+O>k;y_WUXPro_4QQ(nJK&}fwRgXyJ$SqD z2jT?3qNr^VGZ;+RZ3T|{%asb5rL;xMaWw3)B)JIm7_s<&vKkTSQDBj>9iKvuS!SvI zm$it*=ts$EPTPuksm?7`gu2EeHls-O#+J;HXS!|qfG^hM(KkvusZw$^@7BpL_L6iF z5eY9J76ux+<0_mo1`Yuw3QR18+NMr6fn9CXeI*28%r2oe-&&m&*+>}ZWZ|zvP2*32 zlrIN6-D=#<>G?as!%92oYvy1#bn8L0cW#J7pQ-vaOemp%@N3GTLKVRoT^%0${#qFs z`W7^wa;+fue!ii&#;vYhxlGSarhP!fD^aV(|5e^a&)sTMT=RN*vmo6CuJ=AUucH+FXC9pyQonJS&rGWTNI)xW=cPkd2XXP z=DZCUoRdUBf0GgK1bvJWuoSE(pVf3)6Me2_SIYKVOP{!g&I$*ecye5Gyf*1ulrY_H zp_)C0iFP_Z$&J^uz!Sv<7T0=*2`5*VG{iKHY^s=7rt!y{z8*t7+nFzA_jkB#G0htS zC0B$C@0-tKz_(lS1c=)Whg>u4qy$y?^*zv61RZQx+Y|I$BsDE#^L!<|CS%?zzbzOswy~5KCqVcK$Ha>Pd4NLQf}N zvoY`-YZMEen2A+a@?yTH$25fG$|;=V9qos)`|e}E4uZv3<%S-zltHqm?-h#?-+znw zRG5F6haxBA7!FzMPKGFcl5=wPy+P9Iq5%F>R1fMbq$SCTah2P~#p5?y> zJ717m`E^!{7kwp5cC%=gJm;#KetFq}pWDMI+q~r))NSBYs~4>P#ssxW)`}~2sdfSg z(p}Sr?DAvH*@b+Gj3}%>s57Vv&S1R#lQgeP_Z>tNaDRjNGE`A_Ne8YlZ3cZHjgNaA za5MwVRQ$iJ!|@?pvrhHqD%d#h2lhsxsx5l-Bi~J*H*!zG55bj-;mQ*psAXafL8yZ3A0-uJ-^Ec6WhF57+w52 z5TMLWU%V?C1YOi-ZlD>GmkR1tecb1=GKMvJ35fk?Set;LW?7N6q%6-CylyZ!eV(Dn zFy$hC@=QkFTY$NV1UV~>O!{rR5oWYG{mt|bFPFU1BCjNi0)Vj~IMZj-~ zMMGey#m({iiD>Lt@zw-xDD{l$i)=&rQ}b2Iu>5zRCFj7^7aFFj^$;bh*SLtAv8+4G zU(%Qhy6kI#C6tX@%qQfRQ>Xin0q?y$xEUthbe_ucdLS$1jFHzLEy43`_<=`2KKgM8 zWo8LSML_z*Sx5NIZ_kdvv934n6)^m|KfyXXvEIDyG44}FURz(p{CZ($&z-+c=>=v* zJXjoXY!GJm*$qMw!G$ z;Niks@X_QqhEn*1fS` zK(MdJ{c>~4X65foDjDXopXbdXb0fMIoXL3*D%>1gHvBJ+s=S3B>0&j=MrsvxQrj)N zTnw%z!tOf<^Cq@lYcu!fFg25$xROi0|ot_}o#ow@A_JAjxtxe;b09vg1lfyYbc0K?A5ZdYr!@`o<^?jB-F;+hMqZ|mIxpFLC z5h_xCk!O|!G`M88XIHW|GISfnff2CI&+5yB3-MlQnE(I^-7BVQ?z|M(Kwu*L$9$qN z0wi&2x?Q4Imz0hh2X*#2UX^!8l?m)#u0@a0xJm`g>l}qFlEJ)|>eaS+Ha`<`*SxvO z{H<&ip?@c6tm#HrgEKqT4;Pmu<#Ny8s=F;Ot!JL*hNPc>$U&S2^*ld zeNaoQne;4s8dQY4Gb8+L zYuL)o&-!g7l`x4gn3_Hb_7t==GiR?FEk9jkhV7Mg<>~Ysm$E)Ll;`d(@}_g^NAmL` z7^N`W{u(uoAs1~OA8(u9wAHRKIM%GZzTT;}>eD{O+Vn&pgtt>}iN!*4n4BNWy^i~Q zU{GaC5)UcsNPO6xnG3NoQy%8Nr{+IOfa6w>Z5#CJ8`af6aAy==`x$~O1*Kyy=@3I5 zwF!I@L!=+>w6)bA-aR7fH1slw+%%h<+sMJslsj@+Z|E7h2SzpR;UH|>t;-&N>$nF( zW>&mg?%k_2;R&2coc?1irzXXRy$JGzar`&2^@1#WyEbXnZiA4D0l|~1?}b(lcltwS z*C~4G)s>~V4PfvE9ZGygw`vM*?A#Jke(hfdH?jiy=KKgE*1&t7fGf@kbbn#oth>{; zGcF}&6NHqa zuy@~?n+RZ{FJi3%>z9(%Ea+xRdDtbs{w5dO9uMSN4=R`3jt<2t0t{N?cRa4Q^Fk03 zhgWr@hB!XcYP@d_AO>biDs#RoN?Nbu%uG6xPz}o2Eik=Bgo?S;9lh^fPD$dar0a}$ z#6=r;wykP0X)w>^;4}`YR)3JIe8IJp8RN|~#A$rY0XD;Txx+iTNwd#+?)iFU~!bpQDK(~HR6_JZ42_=cS% zOUCNfe!^LwZKvR#+R|o{QF*2ws(J0(&lIPp)mfhMndFSe@&9o3rHUC`6 zqpOr5KgFI*WIE#KZ@xiXKf(sj*aRd|TL7xd=2Z3u7*-hq_23H5sT!_(%jN;gfw&xL zhp-E=I>%5Nw5mW;DfFb@vqLcw^I5U}74?0vEzlREU`LhbR23}j-~HtluWf$b=@RrJ zWjRhkGEaPvW(o8SCI0(`C+AcKBJ`amg-RE1aR-Tl1hpGYPQH>^B_Jv5CboffjSfFO z&YAzZq-52MwE_N~>gYMJz%e<^xNJz8#dO=PxO(_8Bk6nrx#$CX^hG-6Jj1g*O^#l1 zQ{~AiA8MtgT2Orc5*Uf$@BZZN-L2$UJM9dLAZUsDS7heu;!C~G_e8Eg2;KB$ofUoG z;F9m!QF#P%DG!2mGvf9^uo?`THfpxMWfl6iZ=`OKNnRFbMqD^RW&6mVCpP79E&);d z@!Q0$GT7(Ik-Q)Xbx_u&`II3bA0VNJ`4`X+${EHD7Rx4i{p|vsd>DpaSvL)68B(q@ zMQ)I{a#J!}o=EOy$b>ED8Z33GoVh;Nq7b-sD7HcpUjFH1w3O8TWnlp&Y=5`4d`EpA zG#q+}Qe5~@_VqFl)L>FYmHx<^sZ9ue?166_y6b7;;lKGJD}PThwk(^9=t4A_=s`lalG&s`O{6tfQ-V z?si9~>9xyDFLJUPu6Isrk}bq?N+olq1DN6-g$gyOx)`6A$9XK(HNE^wllVQl>bRSz zxl8g~!=HglKhtYD;|2)8r7fP?gX*O_g$17?$2FV$>m(O9)bY?*b@%0^;>X=Lfp9Z> z(sN{+WQFxiHARHHU!TxpASM1T)<$w>q8Q?bSevc}AntNB>8{GHabdzxvQc0z&4ECm zlNFiyLiYV!%-l||6Yn3gy5W0B&R3R)gd(WCo#ScWQi2-t>4k)K?hhMRTcbA1E!D9jtMEB zK9u>$TFm2-1x-U3roTB0C#6(*NckMmCKn#Qu4m(K0A^YD@I|>_E3h^y^E7zS_hzr4 z(VridwyDnc7J`8J4ScKdcwHN}aHg|>ClJv2Xu8j>Dvu0l{=NP3!PwiRazmZ2Id7A4 zR?}(4?5^N=Apf(rncQvI|GByvl2W~(x_`I$X6>^Hjs$>;^B;LE;L|EB@*K$K$>6l~ zu3Pdi;X&2AIa#kXazU^oaI5bk^57_6&Hv@Om6XcO0RZm3C5NHbvasR#z)>kEvGsfA z?$s18A~_onKo|rK5ZDHeUu>~)g+#l*O4MNe!L{WK#j)%Rhf5{Ow!iwUO%k%mHG~-k zfc23l%V%ka{)aanC}~`YwJ4!qsO_w~+J;ml(iM3kb6Xnu*()#0YjG(4oYUm0?k@Wg z`6=0Ge1n=uo-Wst+fd%lA(kQR%4!9h$J=aFh_r|a+X*HNO5|F@t?T>>KB5%*+F${` zNA_yc>mwQ%-dOt*AeeJQ-t)Ja$yLi5hDe^-k|Ljk1{A^xa-ElHN;fh)A764=?U zsP*L^#r*>Y5rcS#y7QoQ==!zUm5jvu;iQTlDO1f9cNO`*!yp^lQb6$G6t$_y|otkc@VD2s_Tdg*C&y97}!at)ydM z)o$;c>G`ZZSg#QkxtFzhyW!?B5l-lw(u`Pz16V=t)K5tpnaM^hfM(QDG+Bmid0{eJ`OJjT(Q5xzsBu9 z*hsc=s4xlvW31AI0RUu^Kr&)VMggV2m9}_FL8n9mY53gwSWHq7JsZ=nhV*hcO!DF4 zd6f|7qDvQD$y!l-9?@p=NEvLfp*s8>Sc6`JOq_WqtS%PDa__ldaXowJRzq#K^n(I^QJ7X)wKZG>>!q9B|E%9W0aQ z7hYRY53;5KcwA$?vIu{VAqlVD;LljzsAST>h9$gf?-rZfB|{$f*PQ}WhTluj)^F!k z*M#jgqlQGhoDvxTm*QKgvz1<^C~u$C=%X%xMD)VH+wEpQi%n?C_r3EV)UAz3s5dQz zI{`b$@pMIPv9k@@2gRgo=s5=axA_h=p*F_OsL(JRot~^{W-d7x=#`pEEr>;BLA#L= zFW?;Lx4-N5Lw>(Fodeu)zC-K+@2q&l6spH~&trvVKGFl5tZCW7xP+0BfAAoE32F6~ zr_N)aSu-#F_12+(40m-DAH-n(VC=XttV-zDsqdxPK3}x2cK*>o+KZ@@r{E#*1Bc1~4SZYjnM31173V<%GE)yAaU4p8PkVh)wE+A`I@LMr_DQ>& zL)#U~=$$9lcvu`)@I4#tOf{nUo@8|F^nPXD1Gwi@-1_v=WRD)CZuY0-mDi%39KUit z49@@Po8pD7x%pCX2FpJ!oh5UtO*Z+dtx*z-A$J441ROYicL9J^bugIEnHrpeTN z4&3(lg&ievarFa~K%QhZ=xgcCFemi~CDid^Tx86b4ew7}qJ0|@b1w zA58tMiY- zs}Al*tK$`p5PIm&-rjLGDo%-MfhQ=k#MZQn<-=mL~B2-UeL%QJm~pqp_TvJ?n{Jr~y{jhJfuvi13r^heC=Mm&|LdEesS z;WTbPjz4_m?UaS>DqeiY3ZYQi(shP&;25`m16aYPDlUv9 zwaWs+UWZvDCoWx$($x=$%D-)kPB@0eRHw6COLNLGGj)5zNwSr>J#zE~GCmN*%PNT-Zlgc`} zIYjb>IUBu&OKmF}f2E={1!DjM3vS!kvW6{gFtv;V8mm4&=#tHA?R7q*H zb2h{W40jlLhOs4mM?UOeYS1-)XDt;2X*x3HOE#?=VjxW*hN#%$KaG1aRGNigRr|Kh zQ%_xsvReb8yc$kxq1#LxWyZeqlo$XjUYv5bKC&fzt1&|~l92p`DsI@DI_8M?At>;~ zGf0M8bxe9gHDnO8u;c%6b&ij91#7r(lE!MBG`5Y#wzFf~w$sK=8f(Ygv2EM7c5F3l zZ_YXQe!2g^%$ixV*3A37zvmq&;Ga)LwWHhQ8-$&q_owPcg?h|%vL@H5gL#r3cNoY< zQq6ASP?$}!hEZ#L+!??IbQJK!`?_CwW#NcVl1&{7t4)Me;%qm*cjbx3s;> zhDu3~dv;hhEY;H&5pj=0%>UR6rvqe}&_C zMe@v8@SY;fx5AkpI_7`1xmI5s`iHIt{<|ly2T{5YYHdMV>^m7n%}3GX86E1)3qQ*v zI$zx9qg_K(%X|d~JJe)$7RA;7XJLJUqu32xU%ese_U~K0P>5z_CuSXR8ClY*7(Bvf zD>#OWtO%RZ(cqUquNt>P)Fg*MMXA!^llVJREFZ*j{}#kAT&k`1;o3OcpCWJt=+stWVsL7U!r{~h`q6j8G0+xW`x_NCn!f31&z5n^>yS;|c_%(%{?>CPsbm31SQYYJdx zw&`(G9j$Zj(cNN!sl`8<-A#9*QMFpN>_5#+L!Fnw9!+JDBT}n6fpn!lRx-A{jr9Kr z$61(0${%PO{8IJ_M~KZRup?~bUyo{YsE$NXYr=Rbu?IDz2@tD|aYDzB{rvmh)?Qq! zD`2SVQ?=0W-^ZJRn(V1`V(*6ZiWMhYCcDEI1PPHT7-Khjn^ql)M%F4q|J~si|$; zpXL9g{03*FH<&~3l9r!G_&gaoyVxuK*HOh1R5P!j*tU!V1P>yqzHqmW{x<71f9F1aFdC#ho$Oq;OQ=e;g=g8 zNpendaCwSxvE9;5iff1OiMqUJ!vm*eJ)$g=fmEXGyn!H+{>Un&SQ8t51LcZ?E@UqN3U*Z3{ znEl^kpN3jkfkH2-+XV*O%YLmds|+veNHg^fQKZ5L!{a3glJnPlIk#SK`wo0^i|PRy z+sX3fHo;Bnnovn9Erq9_dggz>ng1=J{~{r;0<)MkHt9sB>jd?>esYt1XPGZF^hfPC zA2$Pt1CJ{of}8#`{zs%wm9917c3Q#!(u zenaz&`_U)Vr*@nsuuVEWe?zOf>nW3jnQ*Si6XyWkYov>Q@g<#A)+A!9?tiP{e=qER zP4>Dye6bys$h(Z5^iI)Rw>_UcK!3xgNUFCvsbZkUHqofkLA&bt*L^dR<%se}=tg)B zJ(`99>S22rW&Zyk<)@7M-A9<*j%RR}kCB5WHtI}Ky_J~yK=s+%&AKl?o z+;qB%XGZwM!If%v+AGVA>Uc(4U5K!1Ox&BMq2hC3-O^HkwuReEfoQ6Q<$3ZcFZHzGih0+pp@eE$ha--C3Y#25eP&feEjU#0v_Ed)#wb7ddMT9T`oW>m^VL+|)W^_;=XKvc5;DBXN8e7GINZ|-dxeFmL%2gkWT`(i#>%8bre)YO9H6Mf{q zVyfD?MZ%%QI1l~%Upc%nJa}$`AO?L_qAMRS{>}TFa;Nj)ivF%>Q7@}gFRf1g5b(U@ zJ{r7ijCzhK(@3SDSbi1I=dMttUrT8)g2T1*l``4oA!CXK4Glvah(onP(PlPMcV7m{ z$+qMgHo9P0mD0j7*c>(AKYr;XL+R;|_ejsXm;Ym7l|qq4ugx~Fitn4~3XV3Gi0EFF zXgoP*!u=j>6G0$M(r<6CY_35sLY>lRmQVM(no7H$Q|sZY(pJ)ak)n4HMf=V17|o^> zN(zBg1q(6kB5fA`W7A#aT~cfin@SW#REdA1Y-=W!_K$G+LAyqel(a1sbB!}?d+RF7 zfGg}ijtL!^Fb8FKUpEh00p$K2<2cob7$5B4dTMSwaBVJS3FoaP`7kp`MQU-;cbgR? zLs2(6kwv^hVA6(U#cz#;7(tY@N}bcVVc)oq8KiE=8t{M(i;RW8UlHW@226A*Wx51W zZG0QaU04zaHOm29u@uaEl&;^c@VUnb^wGKPIpRw)5-9T!914;A0%w3K(3T9pjN$ZV_-I5zga7pR1s!4Zzy)gmoP6W8AhvlTW`&hWq`j?~7g~(NJ{%xZD#NfL^(MM6>9Lh}I>N;3gG@(ZVlq0`3lo z!|j{5J-}i3)j|iKd7L=S+%jce_y$E@3o?GOj?Xg^=+7672oz2p-_)_-v6jsqHa-85 zai`0zihld(;emehpxzLQ>qO!#R#hega1nIfyQ_2JPbvpFm+x_|`FD-7M?xaAN@3n( zcV1^yMCKI`u1RSWe+u$y(9zTq)Rg2WPON7PaKzokLB38`3V2zGE9skzA=4~0wdDKU zbVkNDIOud?>Sxw{?kIr0!0D)phR)g>p1xoR``CGrrCw25lW6nd36Wv|3VD1z#WnK7 zhLyNk0f&L2ty3vEa~0<%9Va(s_Y3_?;_?Y>sl&K)Lg8KwKZ+*prk|C6ICq-XUHOJu zGiNH=iT}c{+44VM-HuYZevxV3!GUVhiY$W)`*GhajdW$sU) za}@m(02-Z8e#tlYOBcjZ2J*&voe8YXAD3SBJ71~d*A<(Bc&q$(6HURWS_ zXzyKp+FVO&v2L~c1HhJad&piEYfY-0g1Zm{G3*2~m9aCw>?xP+l7J}n zXF!x2z;?9=p|j?Kx!c#V#68qKyR&M6!&+gPcPUK(G7fX z+eZTUV6S{bBP{oyv&8e($ANLE9WYCXODyR2TXXt|SB!22i7XBiM}3`aiI$G}>bW*- z5Y^~vbZMZe9u^o~TYxq;&EQ)eWDhl-BH;xNnr@2Yu=7c}wEH`6ZvEABZ+Uq#BWcW% z4)O-=K3L0MX*R$ST&Tgq&qnpxTUx&VRx?gHGw%?dj zt%|H>I*m7x5@$~VcNk=RCk%1kleY#2e@w`(vDnm5`Qh*<1s6yDbTpBO{xd9ZHl|<5}jzYe4&$< zL_=x%37~Vh+56x5T`<4TvaH0a??bea{A%Dam!U7dZTHm-iN4lCvoLI@AgF(=bb7dQ z9SGo?LO!42^v(D5jj>_m%giuQJLLgA1xKsSx3S82A`zxX6F7KT0)PH}>De%irzYg% z^6mN>c)<=$u`$Nx;LG4hQL@4^Fb+Wyqf-9`l=T_IT; zP{JafhCUV|EGz%+0s)h@%Re&+Us2Lxouun4=ib(Z3$v%!1dk)Z4+7Mg)K5ZfGqrh& zLZBO|43y|q87M<5sUU`g==9Pc7)+vvz0LY?1Xe(EB5Xy6#M6ISS^Y(|2G(_8lrB27 zzo}#|P&gcN)VZ*AUBP_sOO!=lW@417Q%r;j^zvp&70_D85txM$#@t@`gQYg7rB3Is zD)L))Fv0nX$a;|!FAKq467+8qdOLSo`+OdjsFnsws^FhZrK&0^C@4rfNG%XgHT1Ec zojmqfSb1eMkfehv45X=@eZOdDS1yjs588pm^j>hgvmAsN36|`{Y(`K<+6E=V`~g z`bviRjfG9OgB{PdwK|7DBg6De8fuvuBYhx~3Fy3}-ot0;B53@{!xR-#vRD=s7Wlcj z&3-2hfcaj=_ZdW^qn;RHDk9Z%m|s8t3Fshuzk5nCuKs$MHL>E{s4L7wt;R|vx5veS z*TM=<%q!gtge7@WpR1u#!mJO(k69H;Qfa-yK@~x z-3r#y*tQSHv^u^Yg<Cr~3a0+c=X*v(qtt3ESu!uyplrb!`rwemwK zke%|t6CxC(5l#}6vs$at$yI&j28R6A7rN*db}aT*(43 zGmSyX&x5X++3M;HflRkwS;z;aYWl-mMOVd{5Lb5ih_>CI z_MI-OCkA9-XHaz=ZFZ1JK^)|Dx*G_BPqrRyZ;ZrK7)i*JD)29QUEXs0^PiYVRSk8e z8VATjn@5wkMWU1GoS!8K7FSbnoTl3vx!-A zn3K}zd`}Pt@Uax~gJ9Barn9SOlj^?oPlAnf(yzBSMV4%d3kkOw zmV8YWAC|CkFS4`Kop~mF*f7NKMCSdA%4kd#ab9YDvoV&qK7=xjIaW#*9=XMWMC5Nx zN-tJbyILm8>E+dvMqa8nox!vfDyt{{_U65Q!s^q>&=bRjk5N;RCuY(xL(Dg2rI6)* zDHUYeh>Np$oeuhA3T?hDNXUwsB>ZGMlY)r|Qmkb!k}*8+*A%z1Nt|4N$2-DnBCxfv zVi+)N?+H`jzA*fC5y0TU5}xwOR3agR*9#?`g4biSk23okA;A_(pDE|w2}wI&FO%VD zcr-(Via#1K>w1WP8trI^yZzkwvX`fST&C8(aY@fzc{{YZGl%JEZ=BH%?>^5v~Wj*2DVD-AJ_O}+5Sqx z2A0ghONvD<9I2LS+DwrdTK9)1jDpR3-;Y^s>68#Kt0{FWF!J_AY+ri^pxnD4U7Gn0 zOq!KkOtWO2__Ikn$@~&jYC#|XXk7xEGDQ55OJ|96>JBThfUN%Oi2Tgo(W4H<1n~+h z2jUdQ2OGIQ_kx0b>NZJxfAi#G!L!pMWe}>H`PTR{`w})-)>UFI=Grb1SCyP_PrEv= z6o#=CC8o(QT4U%o^T5ad5lkn8id{<~vmbmXFMC6Zuq~vH-ZFK+N~eB}asRUzqnyD6 z7VqDtnR_XQ!}0U4VDeR;r5ic4rQj1;b#MQWzpa4<=%ms&68 z(ZX4o^z|1}rT)nKRUDrV8FI!VWa;`dgV9^D zt~-48b=;f2V*d-l=cR1G(|6iu{3by%AH7wmG$ud>8ESByFqgpkh?AUdqf&oF z7r9ck0s+Kng+&#Q=HImT4NJrJw@s1~E{@+@rzox?kKwi%)r@o3 z2T)><5w#j=gejew8SqQdt+H<7dzCR|%-qju=r^OlKl*pJQY5J29R=~WlYwxX$B9%z{*uqxa^9M@r`*$i((>)wdH+NZnz{p#mkC~ zb%`Ljgp?fOC$+c1UstYFBKO8z4FitkoRn9pdt{9K6u6pQ* z1|3fKKvt^l>c|$LMbqhC&|%h*K_v+(+yWQdU&VG`nFRxRh(Q(MZaacLW0jOn#R)-i zqoayxu_?|)@yAw~EKO67aXR9JCP@}k13eD*GNk6^GjhLL?TP^}o zWELF<9N^)Fn@~RL0NGo=D$n-CwYzj>b;|qjSbK=kp9|i}{uu6J*#2}TDX~Oz!C?TL zvnh~FoN0fcE%UX&j-o-)%?u6$JgOiYk)ohmKsw`GTU==0AoqlIW;9wTA=l4cDfc1{ z?VM!T7V}9;AOwOcPP!a5xG%7t*G&O>DLZL?7fTLSxnAs)U)krE%HXr54>`@hzmN0- z7U;=u!P#+HiaGo&?iTZVC(=#)T}drPpgj(a=%kew-|4nI8Rx@qN;XEnteTvDWmfUi z%I2s;$4499SDRftz>ED(E+wui2r3@sfg7%+H6RF)SI@*mHOnNT+G1?^9y6f%nI{^3yK=_?t&6G;KroU>4@-P&i>DtX9V9KrU^(z@f zUi&JENvs++ZSHjvIXuPzj3e|o4vd2Jdw+je$7%;CDsMN+IviA!fG6wls=sJN^P;JX zAam;8MxIJFXSri!09A88zuc?9N@wF0Gnvxr~-N8o`Pi5u{n6UzW0 z7gBoTCX#A98*aH2jT7TNx>MTfa0KV7{Z&pDU?V77#MTo;WJM-JP)b16xHEfc#de#v zZi+-EdZ=5_l6?O6X-$DM%3^4ed+`(B1}mGW6MV^|;Glkrt9bN})@gD!lAmvN&JoqC zuh^I$_EqC{nT1|+ST5tw-u8~4)Wn^I9ka9^>?kTB7{Q*l7nNZdkrSw5+=c}|%TS16 z0+r0lwVTc1B$3O`OfS?h^nM-@{jMTfnQVgTX7)1c@_U-(D5epLuf)>(qZw&#er#>p zMp%5wPzBt?$bz^`(9it3{MF*X|lbUO=M&l)#Zq*ugu4 zao(?ruPTrcvARJk@Z;?yfmN8Iu(4FMUKyLBKOOI|E7CB&;xP1Br8u?nY?ful%bK2P zqQRkO3HD>PR)4WZ(FPHN*QNNX;9+&psvR|}X!F%eZ}n=StBLRP#`X)0fkvN7>GpD{ z#7_#QM#~p?pxxx}`s6BB!;GE~RR?KoH7U~vB9H#xg5R{0?PGKzOPbXPVet@K17ea= z0&gu*Mn~gC$0QV+F3c<&fB3jKMVolqmSpc1E#nw8Y#N&z3|ZBHLWYkvske8qB)(g( z|6&^YU~3f8!5vs>+Q)2Z%h+8Qwk1pA8VC+T9UEy}@DS?HxaSR>y7H8>v6F z2fE3h@f-v7lYkqYHY0}%#^?DD-JE8#6^Derrb_X5~{I|&9!&l_l6J|Avqgtb~&j(udCDd`>^@oxe^VQER9Gi z2GL+t$B&>7y&webkFv&@h8D4L#9Xtr*55J-3mL294STuoury{O9AV}+u#@HTJbvWK z2g$@;OCFWhs{moli#DUhc-*mf!wT+>O_D=7jG6)m0o{xTdGF4xkk(z4Qk68EtcL;Ua3iH>J<*%lCX2lq^ zrs_5S=41Y?6(#GzVVkW}n?y$g41%(?o)(y`XLY~943p3E{WF+cLb#3Iw80{qoYIJi zd1~L#xv+RRc`F$D_YnIOI*(v%0s(CILs(ulvAdM+`Wa-*$JBnDOn+(0W$H_-DgpP) zD*ON`4H2qN*Z*KSsvw}dU++!B>09C7#l9<1Uk*UPiE?wY`O1vhcct)@wzmo*=~V9I zhJP1&G#l=nd2QwAylJ=^9{Oe0+FR(iOWQ$`eO=g;?G!b6G7`tMUl?6zyi{UnysKpf zb!a~N{7e?-*%jW)i$EnDS4M$UL}*YNdVh(8euOCH{F7dFxM%fGDaauUBVb+GjBzd$ z#4xpY(?o7?*xjt1H)H&K!>;BOU5QIEQCNi5!6c^g8mDm%TMu8vB6q>SooXah24rJw z)Qr)Hd&Kv_QqkeiG*pF|sfrk-0oBjF;vM$ZXP*+IA-mNIoC||e4hxG_(Kg4qi%rrW z@Lfh z!rc~lbE1c&!kF>9MBE9XQ+TKLh2~#=w8Hus{oEFiQXtMAx{$*MbFOlBuDTmzGvb2d z%o7&d4+*8$OgG7CC0yo|`4!Ysqh<;0$OK%~EK%eQYiy+hDPs+)_f*ukiU^6lWc|wo zu?=ODd>SXgFfKq%Uiyr;4qDIZ!)=3pDkk5bzJjb0!@>fk_xvr2dTfSKQgjCc!%gwJ zV6q@P^VC}#BD=$CFZFCU164zOXg~b7!jnQ?N3+Du;p72$f2$wKyr4!AT%o4rQH{K9 zPae(G@rB4Hbnn0w;9_&cdJLL2jjmf-`_GWxal!lt7n);L3@GO!TUXIOn9N3DKrihp zJJfTMK_

k>^Z-`rd;fe}S(2oPDf<2O6j>_p-0nCbu1=ZWZd6H@-OCS*^}mfMG@A z&RaAMHAQrDe=z>3k{WK+(b-Wr&M^N;Ij5KyKl=iu=!`~g8K3fou~xKFsfn80OADj) zRrehXzgg=}iCMOf$8buqR4;FSdqNqg1IP@U|LB zeZMGBl_=@jDebL(#`Nmx=4?B2bT58kcXiEhCqkJ?H;2EtD}>AN zA7CF;^D4=^y@y4EYMwPx0H`O=jfrHCimW%yRdLA|udIDj;)x%W zFb3z}Xu%UzRo>}pVd7v6(6|>8T(Te*8{8ULoUt|Tu&2JD5-Dol%^VuADU~0CNjMZW zoLKTsj|zL+c-#93Z8VCwAbg_(DCs)&nS8u*RW;<{$OFw48QadKRpu0;o(eH9A<-#= zzeSVyqSXUK=0{S$Y*n@Sm&=yh_c72PKV@B5Fe3JoM!9D^%NwET^*G;#0icA0I(xL{ z?iv7w;aV_yint03`Ap#X9uUT_3Q9_qEbF+14Tq1*4~>xtciFj%(W0_Bh^Dw1kE|S{ zH(Xc6;cSwX9p3#{^_eh!$2lgoh$KM1$dc5$1@0)vlC0gc7F$;l!-?|EtE8zNC-fKu zk>K<%^PC%4O`le(g36BEe9A8xSF_d-Wt0z9^~Q(Km3vwruEe%CncSqYZGv)3S25qf zXqWWf#_=1mXtKfF#z{Qp2u39ei=chrVp+6B*68@l3X}f#C%=7QZNPh94^>Xa8wW$A zzeB-siTPEw&mZ~FSk)F2L05*Db0uVn<9SEW^(36;szPSi)+~sFP~h9h+)_&o!ioS3 z(a|liuGf!$Nqt#h9Vl^UZD{`lamJ%pKtjuTtC2d-hW&#^c#b1ACG#@GG_ z3b^&iC@c3S%H@y!2m-$WXr3l|mgR{67{wqd)qc-j{gMOR?mv+&i5+Sq51kjhM#F(Y zkLpPpigJb)gy_Xwrl45X#xXs%)B%dp6IvKFycv9XY#eJ;5{2$2OMQae&NR8TbptN# zjO}yDITnI-ZH3kSPJ#HZYN~T|qm1@X9xjibQw9dEV{L1$Xt92s@qTiu<0)C%sQ!XI zw#Po8U#X{~&GDoyZ__80UDM?AiSR+MB&<=bDk{U7aS0~M#--X&Z7E|fMh&T3!Rrop zwV`vm>t<8X@@w!0cOW?zA|6d0o@c{+6H&ImpiG1k=3_HRWaV#WY7zy&nUV}Tf{Dfi zHl(dE#bUr@s6y^wog~uo?DM^}v z4m3M9|L&?&_EvtZ2Inb!K)f_6PKK+9Wb^VAsih*reZ_o~>&NeADW*S)nJOxO%(5}_ zGy{n1zL=?oYql0@{o(H=sMT|L4e*|zLmszB_F@q_p-|(=0LJyLW--t}7}!g}8ezj| zua@1D?{b`+t8pZT$ey-+)0RCJuf#9D!P0*`#OCHWnMqdiUZUIny!Q+}AA4~yjA{Vq zrJXTfxly*7^VnW+wDqW4&r=^?E6)k~f*;~(9qHRU9dheq(HWv6%aq2P)ZaZrG+H<` zx!c98&@-&f_TL570>x-akKQ%_gS*<2Mi7$<^}2#a2V>ix?5xwA4*<;>O(vA%u`Q#A z8;V8g1D~dJSyP6yk)Z0awVWWgVnbzSZjvAi_JsX!Gm9$9mDziXL6=SLunCq(i|_#g zOF$dTqCxdqulGvo$+;N>^ka=Njs)~C_@96IjSa&ZmaX4^y~a;c{8e21G1EZyp`|MG z3cChbwWeix`d#v3QIFb{U&_fZIGT}6)?Pj&Y;Lh;y6}f*_42-Mt&#QixPvFFH_`!w zT-)JqAb$7Cn=!9q!aZYT*$G$0&cTa@kqlaQ7brjck(Gpo4*+b%J2amq=wfkKw5rwU%nC@F#%z+G%J?Eke7Q6} zXx!f87-hIc0;9_sCiK?O%H-&YTydAYDID0+Z4rG|HoSv$jUOj+<-9}~jdLB$8kDi6PgfJ8GN>8*#l3m^e|sMhJgNx8U(U?>W!qm2Ro~v(D8#+0!KV8br>XK4Wk~vC zM)oRK(ir+z2PSZc>&ptYjNKVEj_q48#w2BBKc8*itJyN98|q}iItm98QNoZiSJk^b zK|CCYcodwBduM0Gs-WS6eF*u3`)0P;n)WX6_HbHikbZ18iLMpB=-S#&=X)cmXXmzy zOfq-z;n9~eu&fZ{qT9=YT_`I`F5zhh{zDQ-q7K%%Ol4PGQ2&Cqo)tj^<&p?Ss+oD2 zLM$RCB5gjAX6n+^^^wn5qk`Bb;rK{YXeBw| zsbicouWHTnCQ-Qy;u?rm)~r^O6c~tgx2u$*~Y=#s6w#RbB{ zYUqIbef7OPuyeKAo6N?_Pp9<=*w?ml7qPr27qy$_p$ zFA{p~Hft4(n4U{ojs9nR$(WLLxwB>JJCPxq9*1j>ZPC2Jx5u)OQDm7+CM?8S0e=E6 z_d=TtU1To#c4w5%e6oydtxi3-npfI>GDzgj^i~^!{WqYmrKYV|fhu6APz6w<0duUS z`1c8`#7ptC9@bO~qIoQqK%s|*q9NviGS9#v&l&TbsKpFYWZvL7t6beo6R&g-_soQp#m;+4!m;?5Vzu=wNy89}iC2E@*0@#uK51*QWZTCk1$A;l;Pvp1A&kfzhuK4%Wk z4cQ_=bJY0?8mpaDz0AfDh$F!egx|MCFb;t`o=7&Rm>fVb-*_F3GQrz_E@Dn9bJ%2X`Y*UKoS8Kc?-u+L@5fv5$9u)^{w3ryX-j|0nL z`K?;t_HLct3T|1XBr68oAS055dC=5&lAHLkh+O$P;zfpa?l!%fJPoH(3SHdLj4vPX{@NPw!EBNTq0p z_qK`mzrQS(uLous4y8VVrf-IOLU@SLDH}0wUzOSJmh4j4Go9M$Dm%kTemiWq;`(Fi6{&9&cHdmJ#2F1v= zv=1InLS0#s2$(j*=}l*`etgwVk2p|$X8r4*{%17$%R1VW6nH<^CcGtKxO?2Ek*ox! zs8?{IHDGfv-O6^tru7HE(j-8HW_SI*lqE%-b8NV34Jj#+m-SLr_=j8q8&qe$V|BB& zcor^g_hZWzx7G+N_9XLq2BV(-ztp1(oJWa=t~Bddb9eDj_z&~rXFF|(cJnbWuQk=< z5LFaIp^L7|3#UJKc*d!#eYxIfCd3NK6PQwh!*T^XYXPqvZ{Y8Lh5h&8rYEcV@A*vNu)e zu0y?g0Q5G~n%`Ujus$jJFH(@ZZ(%ej;dW|%QV1I5xtBb86DVhc=@*kW4Gjnu??D?) z_d(0IS@0_Xy>7jJ$;GwpIK;9OPn;|a}m&TnU3%LmG(Hr1aRakb^-tSI}I z7Z8>~QQu^@q`^{NF^}pAI~ftTmS2;oV(3>-xb`%H@pg{=h^Zf4=SG5ExxBAdz7n?Y zx*VK*dxg=z3`yi#X_-7Hv%Vix2t`FlO#FDK{8MkbF`8^@at(do@T&7$+t`gUJ9X18 z0UJ#_K()l)MOmR}?U-+u$~7fSPjGV!(xup0IPq+Lj4`9cYP8dctD`%PUFGWF6Rk2> zlU+a`V(%QEI@B~)XezRFj>9!j2#a&pkxU7O z-HZ$k>8kyMz|D!qWO2&PvJ0!HwBN?#C+2^nH^1QxWny)?+)4{%%&>31%{pI_O?#hk zJJ@Cnq<9FrxAlv_{DubS1YeJxQfOML=K77-lkpjwcb6aYo>|$yB=auX7hId*GEGjb zkn(D}B)&}9o$+PCBLFn^D%w%a?amAbRl3R2gakgTWC7V#rVb_ZUC;AQWn#QjmXgy} zhlhgi>&<`H=dO&>{nPWp;p>4A7F4Wrc7SL&2ftc%i4=Jg ztOxZ>SdUf_*JBO$K+LI!epz;ZdQd&9@OTx`v73%?Aq!-%3->kK7^g?gAwC*S$}JsZ z_S~k6HgO#u)a8BYm?2>qDJxHit=KrkS(`$4XkXEoBxH`t-p=sITGOE`KahejQLxG3 zo$%YHF;^__ET5*O&01s4y9}AEtzPubn}Py9AQk%J#D}lBfUR)>So0if;#LTqd!}f_ z4IpTX#Ne4T)oUazV|pwKC5kO8elPXv-~`P63&^N)c5}!h6#_vFU4z=+1+V!C-$Bb9 zbBdErEC=*j0{*0NdAa+ax!|N%eiN?@>&SWqr58xkwEd_DqHVz(HAXj@!{d6npiYLg zi{HA-z7t7*>gdf6@Cct9{%Kud{rECyA6WC1H(&+I^h17&cd|fBYjT?Jtxv0Y63<%R;0RLY6Jk& zlx9>ZQZvUv$`&)vQ~6w?VCah=SssnA+3OqqyyjR`ROvIMH-l%bcuo7}(yJflvEutj+zg4mRF+G;0DH6JZ>*InG-=OnLbsau{~|b$yUF_p4{V z)@NSDu8FQRqF3`d0bY{3M*OJa4^sMg2*sEO4<&2Mm93pZK5@(0U5I9B0fwHliLKAF zh~HVjAvk5(&*S;O>iJFEG8_>x9taMHa0e25_0Br`+%F|KQUJ{F*a3QlMi^{NN^xen%Y0urRX9d_SHCiui(MlR zSIldUDcZ5};|nDyYCAg-J?b@TN_EK35YC)ab==ZCgmJP~$D6Nk-Ulz6TUa@Qa7IYD zB;gV@*Low1%!zf^G7ZI`(2z@zaP8kje%n3n#w^NlCwCW)I zk_qr}%PLCTPBJi3s+QitFa9jisqMpTIWX23m+w#CT+V}HH>D614=XkS(9cceZ?F5> zlsYvs&rhnd%+)UHy>1m{&tcZ6k-?Go!!@syfU>ke{teJJ%J1-zu~!URg_Am3{$k1O zL&2|T;I5TFwpc~E@FR}GSiVP@{N|@HQw}rVn}@% zUF%A9+_sdtiYV5V@zrJ2uXqEBDtiw9I2|6#ZABeEkzE`Q0+uob`NV1s5P zfenq793O9omRG~cXoqNqNzZh6SS8Auuh9WU`}K|(W}M1KIwSUpaQtu~9A6UJeN!I4 zcc>&)h#h37|1mZz+KdXW32To!yi?I_v~yGM3e?_NiebV{lNeCXL9=_;qvPJeC-*7M8$=cCmKT{NqGz&U&Qfdx z#63CWb+8HKJ>1-(6^H!Rpd297zz1?qVQ zw)7eS0VZ#?J0H)6)#2~`b}Ue)kR*#ZR@AH40V@+*3XHKHeOzGawwCy*(J+2%n5xk1 znjc1ys1V-h6*Mh~LVN!jy1-d|EQ;aIc_~NaKVBeRvwiv8EJ12pgI%IM;$FVc(ub_% zy$gjrGIi~aV!#ZA9g6mQpqmxTXFuyCm(E6L3upjI%-Bv+dh3&7>N@qd`k)*zEf~l3 zn!huIj{2e%NwMsCqtZN&cDNqVR+lf8U)Hld*~%y&4R6$3YO_olq?dy*W7P!M(k#|{ zAmD({-chzzJ|*wJhY>u>;xgpeZ3)t|oH(s|=t~==wLW*ZF8ulS=q!_SCG@+0HG(kk z!6uRDTb`hkJ&v6{6*u)udWI7}jmM)Ahq{((6%a9dI4Czt3Z2#z#&wZ`N6lT{stWq% zV3U3vYwB_HTg>jdAQH_*yFr!zCTH7`3h{StH36gZ41pUV$99 zBfKzKy7-NZVBrB`=Htv1E1mMv^r@rbH_Tof?)@2^W|i}=@-bIO877yB_y(u+l_{9} z?3At4^z-LD2JSOmrM7>!FsBUM*w?CxDIgg{RtlERo|5g*R6Eea5l3uQSK%7-=$ z)vIt$-$sDBpf3i+ ziSV!t-Z)4R+rcnhSE|H@Z6=lF(T|X4&SXBhM!$1f1S)wyU4<5?C%7hGPN{Q{0Mqp1 zk}Ga>&#kscdckZg?oNv0OB7??GWh@URz(mfd&-e|NLX*tSm^2wJ?L%u?OntQ(Q7TcX4$nQ+h#TSNVNzN69%qa3G^nE3=Nd_%EfQlj!tQcGKrKLzru zM1AJ{L$gk`p!x%is)aoA>R2I)Bme{lcUh<(wF}-rule zbZ*%+GfLjo^cI`8k?@x9&~o2&#cIPdLaTN$f?EL zA6oAt%s1zi&TR5;5W?bM)O-<7TYrVsUEXI00oeZ4{&kkubO@ZWIJ(6lI^lMYH-U0b zUo(db#D=J{V%|~)i8kdWH9(B+|p5qev^{^@ayHp8Egy=Byb6%TN1+p4Z5D?U>$%?k76l$0(fhHtkl-N@hhK{uGjJ`?^SAwJJyL=oqxa_$rha< ze1jkf5kY4f$sEFHJ&7 z{dF9l;YW8+VUp}*q2jW}P^pEfQkPiOo~cG)R?@^bX$lqHqKgB@sXg9^DGNReCIw1~ zmcX6G{{vV+r@yQ)k**2R)nqN!+tMOc`hK1)n_e&b#Xs}o#fttHN3IvPWSffYJ6|l* z=Du6Z49D#^Uw{1Pv%j`?meZCJ=1N87AtHVhrn=f_Gz6z=)6U!1pj>P$t+6=>CrFYu zbw_<%*3Tis9_l#Rx3Zw?_<{usc3Zl1>2Xqic+sLoN6R*I&K+sf=BgrUOEjq3($zW- z4CMhPz!Vr&PMa1s4JTI%W(DjMtpbIIC7EIe9L5#;3g_O6p5O|$(+3&6%_Z~57`@ab zBMn?S7qTjQE? zERDd9>|C+8b{(l0S*Z+d?PLOTB#s|&zyaI+>Q}!ySIXPC)U7t3CtV}&6eGpi#fFO1 za}P?JVhnxIiKFJIp_z09YlR1NI5FBLVNH3;w9%In?Ic7DNrDz!6gATeo~{WJxCm(I zP_Q19!&j4AYaYOdb~;tzj_vuN=bcYl>7t@Dg-fnpymBA7>}X|f$QLJApevcSJCe{a zISSD2P}8ZK*v6ylSvI=P%1atlmgFue;RY-n>sd2 zCUi*`z_nEBRckU@8N-Itj)_cU=<98JnDhgbJk9nJVw7e(N@UyGc_thoOa4Va_ZwnJ z6+xAXwabwQKzCe!M);4*mWJ&&9}9;*X>K@mpE==9d9U~$+s+B+U%MoH_r~R6XW3l7 zjCarW^>kvJ3yoq$*HZV4y=Ilf2+XUSsCDlHwqmiZq9$7?fp&Y&q<4$n#-lA{^oQE9S#|nTeP$3WsJsjXWNSVNX!9Wm_Rp z9+{%JF95Oc4WQ9j8ogH6UNyhp@0^p%efPb3)dkrNvabw!0b4; zMf48Xe*3q7TRd2lfB2vO^M7{pcfRxO^+zB5+4}GQ{=e9K)%{jo1n z#SNkzd{R9yd?C*JeGw17m|Q{6KFuoi=rw;%PxJIv1r%swubmP~J0kIj4}HftqX5c~ zeAgrUNN+S6jU>ZoyENK{nwz#_4$z<3?Wl`>phhReWfJ~ej=VNocGWVhY&vNlWiodf zGP{6)g}$?BNZgw&`(woNSzIEc>zgGLJ4ahHqy2)ykxzkHA1!-&UUhRKIc= zD5t9Jh0=d4_kw%uXXfBlaoTd5CFaEu=Y)JLx-`a8htr*PC$@`B<>AK#PB;+>D`KjqtWHd78sSKz4`YV{QYFOz ze)TD{fz=hU0I&jwjNKY@Sz7%S? z#!(Ja_!YT;)rAXvz8Y}?JL3P_x2~iMOPgr{KOpo+-(O0vi*$mmxL#5fakU%)uGaZ(y56@4si&t!uZ zbQP%vxh7Nsi$v~5X`uoTq1p|k7F25AOeS~7eZr7z-q3Agtwi?lK_C+q$Y~ZliJxV+ z!-JA1o*=943>Le}Ij8B%D95z%YWCN0>Eqc6%;IVs7O%>;5rPzR$FrRMjpm`49v5?) z@LTT@E0*==;1LRkqPSa$KJKzwvhjRrc~~<(wv&bt>gFGMPmfPQKPab$PzL#nO*v*Y zS#P<%$rS^-{OsFxs=A!)4$W-y@U6=e{8|V{<-d0BhRZv)iMLNSOO4`0;bcalPz56Q zzZ##*WVRSaY>nRF#N@aDq`%GqC~Wj)(6G)Fzg!wRX~w3$m}>ZeD`c^I=uIzfvxyCf z#!=+ioHHN?0P~^`Gaiimp|!uy_scOJ*fr9E+PWyyasCgV%!07NJStX2`_PJrN54fw zpcEXchma7=^X3Pz7(aDkBmFP`{&Kqhz-0PsoIO8*o%A?Y9yZ$dOms(b|9cc@{0GMf z#lHZkZojF$&Nu=c(G#s5MPbJa^SmsF30#Cf|Kf}355Msz6ZhZ${IsgxJYbA)#MN%@nP5~Or8;zA_F>b9{)|dk zG1#|>4`@(PMi~btQWE{XtKXV49n?c|-rT@b`>2T@I9qYK6xw2>;v>E6BO1v}sTW5E zFITsN0KcIYTz|4!I*=7YAipv1>YQSXsC0u-`EB7e=7-uSy5i;wF=Mz$gc z0EdynMB=IfGR8LGE>HA|IWdFDTHOU?w9y(AW$?2sXf$3Uj+L&A5H7 z)_I>C;|nVDAF$ynk2CiWx#SGx)ZYB|Hc&K=4s8L7px z0<%a>V(-AWtD3Gp?tqoCJs>Mch+x@M6eW91qeL45keg`gw&-=e3wk~`avJ544Qsm4 z?!uyD&<;mMQSY)QKT!PTQWo+8Kl%ar$$(_Ba&E~b)P*_`X$Uc@GD3oi9NQ(SqkN^v zSqUkgn-U}ITxot5;W*VpL?x}ThQP=Rh9&uy`9VXn2#({eP9pYW_^&vMki^?2>5zI? z6#4{f*==z?nVvhhnI1p8nU2g(V!>XGLFX~;CdkTcqY&&ed*rv;yhb+CnxI~iYZeHb z8Q*Tdh_255WxW?3bi%V6pLV=3uar&9%`K#pCr_qNe&YUg=*V^H(MO*!v^Q8x4;ct} zv{5BzuecY1krB|EJUR|5bsAWxH%PDSa7N%j#K`Mtk}SePCCgfD`ou=lHae|ax6^D{&ozCxVp4<(VLv8tIR$E*NdZOmhGVp zIgAN6k?kGH=1cKE^xM?NT{%K@jH9F5;32g?4{4H*iWUr9sG%Hj=!o^3pm}5bxI%!H zDJedL_=g?9`=RJWuTnOQlX*8Wew60IJ7{2FaOR^583G;}4SCA?nLDzERl}ypB^Jcg z;br7*Wsi`;7f8kxES=O3F=#6zTD&7b)oh134w&)UZXTqeDw(_>K01?)zc`Rjv35M+ zw~&B*(%k9PX-6rkB0X{ z#3aU3f-7L)0^z=+RcIk~RhGPbK%x*?j<`p-lV{O8AG;0GsEVub$Oj`QR z|B&5q&<+3;Cr7B;x(zmTK$LB2xZN~cC$Qq;9PaI$YvUEVGQK}iBHG9~> z$Bn5=1tTKT=V}&RAwg*Uj6ARn|1De8~6hl^leLk~WR*W5IA`c5G-)?4H`r0mf*14_u zq0^+fIRkNvF=}>ED5jBym>NS_SQdHUqRyPO_&`Q^otNwHr+{9kYzg!V0tpLr_gfu5DbE3wD}`X6f5f!C2oUk1}XNF&HhwPOvpn~ z@{50pjdYj(Z&0d8Oe5I;wXt^ zFs~!iB1A}ctfnVk_26!EZw>+3Wi%SWP$U^W#}oD1KZB8etR!JZT=pJm)nMv}4$|!g z((N9@WJUiRH3wN6*Kxc;d_^;?k_zCb;+ALZJ=EGZM~sE_f*_!W#F(0P`vec=&syF9<0dgHt12(fUHfx zr2j4i8s)cS4Y7O(xDh_2A}x~L5>TYZ8nD!bHCfQ^nZ#MRySvB&#M5&`~MTDzusU~JKm;D^V*ZkO9B$A^8AaRjLpKrBYHo(Z z>MW4KZZz$UjPUwXO8_d8z(EIK8RViMM#@l#^`nu1k%owXL{^9@#cJnV@)3VW&?|z1 zj+|B#p>(u3qbR=l4XV098bxr0bmn!LRHlRa)TNBkt`5*5q=Hjwa9JT_RzyyL-?!Dd zUvgxgOa@5;`d>9r4xjitreVDb-lmF61U<_cR%k4BBKCWe9R3dPhQs~p^M=02o$wSD zwr%bt!_4f1(?on|YUpCQ>pJxX-dJKh6GI4Zr_3z{lr_d$bp}FZqYZh^qKq&es9~ap zD6vkQ>8Pa>Gk%J8AD_eQ5Bj(tN1bgN?HjNruS^Z=iKlO73u7XH*sL8@#Kk@CD6yCr zTlrfTg>+Mi7^%tEN7d!3>n6eY$ZxtK#s15WfCis8b5sX%`3<7*V}M|ol^RcoT*Vkg z+DYG{9y0sdCTJoSwY||l#1{l140+%ykcyJor>sjjv z*6*5eQ#q48b6UDlp|8rT-7L(pIkuJd?cbkH-gI-icxh1vKDX#5egN(py0<1e6M$FX?nz|3ycd}|c+Qihg1o}C6>&duF!aC&) zvyEd-?$*Ba2q8Lt23!j^350g!w4Wc!A~^oU8Cs=-Zv|0;KFVUbyTUPod?g=K6Q7KA z88Y60M$J&s9nDM1xE zAPkPD@ZYMitiqAEmP8ukCBL&l=N!hHzTbR9}VY8c;Z^Qde7` z3~uc-jK*kb|G24-RxpW(K2fA?=K|T(O+lE;g}qZU?NXK7N;UAZ4NM!$2CdkQNi=GA z)O)DVPdSb#sop6pI!q$#;aTLBt|-Dr;|##=@|Xi^Dbb(1T}w1>TCvdVZf!D{yhcOh zM@0xEhEa|2DqS()7=M*1y=XAKUJn|ODiXmfqVX;fL0L(ga=Sw&fze)2Y4wgRJ!;Gk zYvn<-gbsqk$67z^p*CrZSk!ok;+#%Uxb8DWC7;}_&Pqf@H@qnMedz5&(Q;vKwRVC< zF(^r~Q-fNm|CMT-i@Xol_UBOZj+85~5y7gfS84Tb7UqcaM4FwQOZ%}npPQQ>X#$;# zju)M4B*PwZF9IVXU^f7t%!bhoOeh>Q7)HnjP{U-rLvT&v7_jX79Jd?|v?0duD0|2o z<0t5cnbnRhPUet|$x-@%q%7Ie6(GT+!Yli9lQIHLI>6~|7rvMkCL0_J`L4k2>*fnJ z@W)iLz$Pw~Qv1v@7rw?*_S!iAQ-Y+RT$@T6+a<-A+{FxMp+(yF_JIs5)276ju4y zrRWEtihn*QyiR1d{=r2vk*zQ4^EE?N4xG5~5ZiKAgenF#D}|CVjCK!rG-qc9Fa87_A?BAa%tTqrI+Gr3?y$)O;fNG+X>V=c z2$pS87H3!<_IQViy2XdBJUWq{{)-X@4(A}I#xha zb!s;Yb4EQ)PfewnnVB>_(-l=&#|AS0tP@_XwR?Pf5vU@-aZvTL3Nxh0PUy3{ri?te zhR{2>PtRBrH}x^O9@|NcA#S#eYk%K_X~IWbT|LLCEw;DUW+r353oDwU6E|0_ze6B& z%6!O+bI;if>Zs^4yec3b^^(R!lLvYzvLfv(8jZ~<@rG8NUyL|A(>7K#qSY$3Vl$_b zn`AvNc@;E*W(c|iJ7J32@Y*D|O+=kFjBD)hU(E!%A zH77AzAOsL5MEuwvn6wDbeQzrb{-TD_MF%rBTU2f95edRPic#4CVs0e_DWUA&_42pV zJt!@wXfw7|%oNl$iYGJguwXJ_;ig%)h5kpKx|44qdbqc5;%y;}?J;_3>vW9h|5jQf%B3V)7eZ1@>>%*Y=yFoN00 z&``I2z?hSeAM_CF<3H*oiYXVYT3@9~K`_EWoi+6?%CJGYOd(8SY~(HcG=KY~)AVR9 zQcVHSeL(KxyB1;cH`+q3^P)m)E@^_U#q7A&T}|&ufXwqo3dUk?DXFKr!5Xp^D){#D zy$FmG0p5(&|8pOzCp`kIk)eqr69N@DNw5jWmD)|-mW!sL@C8@bzFM1Z8d-i8+OY6T zOj?9Gv`lb)8sL|kz?+-K4fymYV!<)(L0-rRMNyYOy-~MnffzkeVxG9hphTv>%hi+JPI6X0KyM``mLs>s|6GtcvM?b}< ztHqI;*=NE+?wDf~i9)?F5M2{H@OTAimyCk>fKclgpmBQwZoBqT&D)Jd`{<3I{5rKo4d8>ogWf3fZ#3&Fe$e?eT#wbT#sg~1%`G$h+YK-Iw zq{YVV^9Oa-IKNV+WXn3Iah$>7hrr=$NG-W!aIV&1Dl!U1TOvg*t;|^!T@WN;85*fngg8&e&xX~3F$ue=w5Z6bgX zp`B-qjOby(Gpu6~-u(oRfXTHS1>Orzx@Bl~H{N~GPq%tn`&vBL^Qh^Z*`XeY&_ zvDah|XpjKMfj;cCnA%OXiC;F!C7A)ZX$(k~EF*WK;ALPUIJJ$yQ;H-3A7##YY9Hoo z%Z|7#))Df-?IEAV0&EZ{){f3>O9-m-OiMX&L$RDF*;><&RVHJ^T19Zt$0*k>(&BKq zE{~;X1W~a(y9nw6HCEH;ap&ERi$@TJCHk%kz^wO;O_S1d<)VqJbFI*kE0|Vik*bjO zq|iKwG8?#SO|jUyhJb0*rV-dFESzWXnTD0v$HoV32s-+r1-sg#m=Wf%Uvs{+zt(Z* zlCsK&Qr55-Uz@@*dvZO^U~6;b(wX$r|MUmx%_o17=CIAVi4T`qGvdSMh5C^_3dDGu zWE_Sh;&LCB-ClQkvz0IabQvN@k%{=#c}pK6@|@V9$=8zr*4UJez#DDY7f|NIy)`Pv zT|{AwGYjrzs@hHtO(E9kec-Jon0~_rUIaRV8Z4q$hV^|RuH#u4PJ*ALk z{<=7crGw)**@(TSD(pi{yym&s%Y-Y{jgiZ_5L7$Lcei8ChQ@$y6AV&gTQ?_8&JhOh z=x4#SoO`vs2=qq)6A?#-_jPqI60(jvlWx1fXE%OadEsP8fhHj)55jyNAySEQ10S%+Lq-+_(Xs$SY{CYm z>y%bw2zJYX2yoG3cD024wL!`PQOkyKu+yfz8`g*;PvHwxVU-gBu5#mvwyO}iXmLPP z6j;|$1ZTQZu$v}ZOgs5RxO%l>n5lpkE|zl4RY9GruAp~Pg4oE`BOEx=9@2#WVs2$( zfoURx#$}G3nSo}4$>8FEZ6~!v5G*qG#jyqNfH1}bSr{34q%tAek*@U;Eql0twnCp> zPC^kqi6dngg;~HQ_TWzTFdnKvd=yCg%NjM<4!3K9578faUfae8A1L@$(;<%)S{U&j zZ8B(+O>t#Mb8d4(3;j!c*1*487pBugci)(vz3@g_nA}W@xX1H&2OkjVnRdT*gr(h- ztNX9Q?LlSP`o}2W>Ay8g-BbpR>o80gPCW%SQY(<`1Gb~zrL=lTs{Nxyg_;zo!QVnl z69BXSB(D<30b*GMG`L!_As|(>#-QF6azkM)p--+F-NxU9{}o1a8sM>*w>hp7gkspJ zrVO1GiF+$?CNanxz;WylNJf6ZUeWN+&&5_pR66p6$(yOMi@qX}%(!HF z0zky6)#O7qY-HKQpiH|6CWMo}R*oi#Hn!~nfKsj5BsrqWj5e^rsh<%iRULqet6Q|2 z4#_DYK1LO`vu5!%KjdFyIJ|=)CWF)w50+RBD-EYm0H%8(^y>aB0Y!%5)d|p`UdkIx zwmfopM@Antmu_5s3?!PZxWEOi-C%13TEw+qU2YQAc*dx$XLJSD{h<=o>Z=etF0wDu zqHv%n>USuuC$x_#NEB_e7I zZ~W1|h1SR+$J}yr0hb))5#Vs7C)Z=1`i~=NNR`Y+*WrGceIO()VQ#nBI@L;)`kKU( z1_*Xx$``CAd>52UpP>8hLFGt>v6p0cE?kv|yIPpT47HO?$PMWRG2sG2li`o?MV~w*|s16S^#d2#Ym6 z&dUwMkv7ZFZORRCArcr~)zTYARD#C&kY%x6*3M#WyLf7b#P8kROX?-)GMJVlYb05;uUW*0v=|TU_nAsD3}NIp zob5_O9!1t-!$Gjbax1h4DsnzZGKEaGm&Aom;Ecq^D36(Fq+!0>z*%$chNF$9ai?Ze zt>Uu*KX}B2i*xEX4R)2X&V}+Kafz;G_j>`v6kj9oJueK{M+e~@BB@= zxU!bgzPWVa^t0*c-Pflz+5$s2aNK1p&BB%$NCJ&(5BZiCeJ$K-i~u)UJQA>jwilk< z-<_FtVcSjtY|H25$`lP;q>FS#d`*UO=?6kkhc!s1lUfPPpn*b|CR?LYQFXLrjC^%^ zGqE7?h5um?$#luq)a^T03#c#6^5AHBAa; z&bn;#j>b%X?G_>;B{s6P*AdVPu4wJbrClw|qtVQQ;(*m*A^z7LRgKkc++CT!$NWnX z0rVM8&D(YN=#QKDv+FqBOgjja%3XwJGL2Ek9J)E72xRrn5Y0^x`ZK@_v_<@#A;P}^ za6avTjY~e0Pl~%Dht_Kw7&CHb8B$axM-yGN_f6ylt4R3VA2wkRj}BDHWcuP4%o_h} zyJ!&PvPW!`Uh`YIjuj!Yt0ohSm$0sb1gbxow7_|EqAERv^~Hfi*$2>=pP{$*gJt*8 zm{3DyR?C|I9)gEnv`4xWG({aXeY z4Hp)2JZmie_PJJDJp}^$qG>RDfTN0B`WQpd^25nt221DJt<|)?crMLN&!n08!|C#- zmgVRJTJC_(9@J@N#J>{jMe1h}e?P#2ei}0O;XL_1tN@mAru_7i-%n?L@>l8H;#yjt z+m{X_|8HY9ocIB%u!6LmR!c zo42H#Mwz&49=HF7f@sW8!|dne6&e~ouaM8|tg*eCYJ%X-FpEhg2}*5D5@sKJ{Z*+4 zyv}KjC~%40z^tSxfPhNFIoC&>iiIx<16>G>jiLeb^+6X&mMa zk2>^4IoNu0+V5(MrKAk2Tdp7tYm|F@!avQU*>2+xK2zp0T?PAG@V44m>+#5TMqnZr zLLgCnkW&yTh79EDgUVSZ*5Wq6S-EaSDH$Le47_;{^_`AQhACIFscl6}`)Q-krji+P;-b(zs#RIJh7{S|5~-qHySV*IrujC869 zY-g-y88vMNj#sd;#&PE#HK;5fDZnCNv66bRpl%4oPBQ(=nnjrLlzLlIyfVmzp-vsv zo24jI#>=;Oi+fi zhf$lHs7C`gQx6tMpY3Lerc;aSWhS*K3_LB9INt<7pt{Ax^ionMflPlc7RvEdBCSTN z7${11Yd0SeTj??zJ5rsIVyM8(GC3?uf&9#l$ySo`#!5dzH*EqGJY49|5Bsiw;rZyH zIDjW&WSGG#<*G+Ow!R!nJ^TQg6$YDVtwOFgSsVyc-^npARAxSXK}i*Ajzo)DUQ=7}c^UIgB`r;;SjkZx4b;SgFbleX8VE+t`tV}PZ zExZcxXW#x-dShiNefVRaO%Htfy=i^TATd<%pIMQYY1gJhMTN zMv__9GbTwTds|$L3EF5f2jgIF7F~=pdvHzw@E^l6z#VPs#QN^U08y57?H1hT=({r1 zVHOz$#qC!#P&UkNoZ8*OTn$4rDnqRG-2U3=%FK6T16Ahl75_^{fSrYtll7(OUaAA6 z$&Vz_)uN1gUORQn4H*qM_z7frXdg91p4i+r%|l}AFuGjfWfLz{;h!vmiUL&R#w$t+%8Oo%3EebbEkXb1JVGG;juNklbC2Tt^?guRA{ z_M_3^hdB^X3R{+f$!b*v??NewBTd2A9u~1MtEjGQB zT_*3)TT^eZy^kW=jK0-xq$wJJ2sHo(y<`bRq}_Ur4n*F@rVscK76y2|{D6o7C|k|I zs`sl(x^aut76GK)S@MUA0j8p*j(3+V)QQ@CFdN8)giC3oF89vriUo9N7?4#lF$*+) ztyqLAaBLsEsu09lsDNezyWd=>@=Z*?kLlj)|(l~ zpMN){3m4Mdk;6Dp!do}xtm9Y;7V&ZvT`Y3>HkZ=ITQ8+2fBv2H z+Vjt+V@FS>-}tS6lD3u?(}l;sla?->O~=4fI!b3JEIsS`!qroRvXAz3yhEh1y)s_{>jRZ{0kPVgYk|9-r94PJ6YfF+M-DHC) zgL1)@83n&2Bn%YVpO71^P8+z&PGc{C5FhMbr@e}o$Az|QUpVL}!D&gS#EgWniE&{2 zT5>|x1&X?(uQr3;3H|^}XK^kiG@z5h%xDKv)XRYm`HrgWjy$IvDHc9lR_M_olm~(5 z>N6e=E3=XTR#vshHdfR%29T?9NJJX8xlzOwkSHPtU8l;rVltqoCIp)cCN%t$Jk+~7 zD}}wqkkv&}j5li@nLhf(uVXPkmF8c2H9hm=zr^nM6}+o_ z4)U-ej$<2KlxuwP$jrpFcF$+mmeM}hw+#E1F*i^bA1-#1qD|~2izQ%X?qX#0WYNZx zN}$Uk3ckJuq%9I@bE>FSk9HWsCpO?jOGQX%&(i-46jxI{zT@F@7ioixSeLTZHfcjizr%d{@P=6NT>1=2gzV?WYb{$%*Q&&A?Mo96$83T_t+lA zZI3|%ct;%C-NIZA(G9j6$;?sK@aR5Xk4Aq-JlGTR3myS>8tYSxH5(}dERk8mI4$b%veCy@5N>s(AErl?Mm zPTlkek!A;%v38c4Fo>tNtrots za!8>_{&`)ZE!Z7F!~}KZM#7qX`HOW)a8i_;o%E0Z4xO=eS`#z;syJ7)uAcovP+eq& z%P<1F7QSe%+?QNViD#iTK|#QrSO3=*=l^) z<4*^z<2r%G@5JOrTHoN8Jg3r$*~zppb1}W~>`&7pk9{Y-`NE6o&2!6X-;sqhgSVtS z|LEVO58r-gITuaY zTY3J}bW(3EB_za+ixXk?%?Wwi@&f}RP4jaS+GV_~ACkRzTa|-1LH&?Pqhc1HQg2Yy}I9nam z)M+U31xMh=%PE5g5ZEFi!R$8a0i$)rpMb_qb39$csX`tpav5!<=4#lA4Gp;F>mpRA z)S5TS$Rp_Wjd(x?VlTrO1@hMN2tggjibQ>Z0zwCn>+`mdFnYv`vvR3`e6(NK=4 zSj_qeqJI+C?Mr?1Ev&Ue<^ja^DX}pB`D>cAe;RK%nZ<7TKAiuapWa9pUwJ$|`{=jR z(i`W~fkX3Y{?L(hVC^y%$k^e=5tDOIK9*iP{vXn9ci)dMf@0SlJK2YholFNd(DyeM z)0Ne8>6KTXOV2&>NLoI>n2sGfnoeAID4kh6pPqd7)%5n+^J!uZU*SBkKk><63OnKl z=MG>aU@a}KT}g9zQ_JD`dAzrLA}!+G<c-K?`7(3z&50F}Yw4sVa#WJEe!f@I#6b&m7W9pUetf6ZFZ3Rr=VZs0>UUXd=%BKweCoVqn zT73G;19>5&^e-titH|Q1#%Hhw6}Tb8+9G3ut;%T+tnmI-A)4YK+U&7djgsGp*``97 zA!|ZAlGLOai(V5jWaq+cu>4lflQ_nw<-n_J!KJws~)AbJ>Pv85?|0i9(cpftv z&V2Kb2ac@lTfm!AUU(wCe)^?!;?SXV)3IY|`tqA;Zf+)>zjQAB^yiPJQ*XYVCTADY z+`dEU!peF&{~}I~L;V3P{%<{TLt4idNY`+b=Fl`GW0!myuU|a3xRU1f@5Ad8r|}xa z`E&&BGj$?uCyO-suW2Zg%i_-n3`@f?79i7YZu1Bh>-Y<-z=jLSZe~<}0dn@E zHa=~QF)lHPQkQ0@f!Ni;oXTj3jB!hBKLC(iFHLK7MC{gVYTiqG5kOB;r(%!d;=>}+ z?Om*E!azafs4ypeOeEdGHyGPst}t3;4BOAHZ(437bxN_@5u#+ERbrB6P5xy1Q4?zj z>g2=Za{NW$q{!kx@{%fM0^N>?F+l?{!|q!A2zl*Ci7~mTtHeW70jhRWGJM~a*3>4G zW&#Tv+1x>@bzOyh2Z2X}&_e(deV>AHU=YxpvqXI$2EB5MR{P5e9qx%=FK-QWr)m(& z+xLvRFOs?5`KKL}%TKYBRQ-vD6Vuv4kTvUYIcBHb&@FD_k{WA&)J=$B8~{yU5tJhx zI(%_%cE})FfDg2&N}tJ4bg(v<7z+_rDDv>bvE9W5*>-Y+lXOh@dy9&jX&$@M2eH(i zTzn&4c=hr0=#Tz7y>fmjeg3PzneKnj18HsM2#&Tard2GUm$2)7;LwfW#fuT~#n3hW z0w^8|H}N@h?n1hRh5XXmN;-4#N_zAB#k7Eh`*nwo>z(IY8>?x4A9mXD78M+p!H*Q- zi=bOr*l%K-;t0&d#+vMqKQ`)N@7u`0japdl%EHM9A0-*J-lcl`PoC!5|;~ko(9P z9mTN;PKYQ#lf^Mc*syzF4rhcZ$37&!YjQZO`Nj=q{MG(_4rBylQ)1Rfx*cqn3v*b) zKi5}A=ubvBogp(iNXjC}kH z2NT(gogOg&xQnWVkK`Dfbo>#PEj}>NY5R|Qk1|Og=R&C^Nrx=jPG;bZUwuIKwDLl7 z?jvNA5*8vNK}7_8MIPb=48_3MY9cpEPYEoCF~|dVSO*H!j&H z5P6^*Sx~4gSYab)8Wh4-6ESclpU7n|`SC%SY}p@y0if)msSgK>1*T9-^{YGy;kYMk z9Y1T&U*cqYP0}XXLmNP2uL$JtV}ce-;|1(kALJK0-+l>awx39+o_!`A#=FDse)u=j zdvToPhP&UJ&M)I#Dl5zB`1C|NGoVg6(>K(R2fg@##4| zwDR?g8>?4v^nD+i2>xR?eHxM(=gECD>Fir?VaNO+9v;4)zWc2|O<%qFt~5P$B;ja0 z!i$}EoJnWj*}~UOry-kfSufGE7>;vg`1)Ez5wF^W?cAmcAQ?r9njdojXWmFGks^97 z8@zvBFgd}gzAQN`Nm0Y6`v~CCKP?KzWx7o>;Wd=F0xPx~53gpe^($U532{IXfV}B$UTONQ zM=PJ1)J&cUn~A4W5oek3ZCa{>Pbn0~(O_@Y;8*}a2mqNAuK~iFI{}useBvJtZ6u*m zP);$J>4qRWCyuPk%X$_}{U$3G->u}9IcWNBWZk$1ttkj4?mnt9c@xuWF#zH55x!%0 zW2Y7dW&zuui3@}YEIR7lov^9Telcxx2|mez1E!Jf8jpR$1z7t8&u}`E3A_ize&x~W z-M8S*v;nB|(y)FeIFJL;R%2u+V;O-DmL|`XfY#-Ps8b;wg%)}%c|F@o5?d%0N*CoF zM({v_&!i#}0Sod*-8BS9E zVErV%+<6FZM>)8)lvZAUDLwtjchi}--%cm)`dIq#m;bwT__`a?+T|8os(;fGv>yQ65J%=x$-i;$SZ*TA&?AD@uK$*}Op!ZF{P%mlJ_#@Y_B#F3K z4joj}mpVdXE-+4G2olMH9QGU9rLm1X;K390Ew^E7Gh%L_aRpwNDZwyZXkc1hrqSe) z4_xSH5yQZmOg{7$tp#V?s%PQFhWIO>Pz;1Rk#YCgSj%l?q6V?zF<}Fw{nibTT{8gl zzqMK+>a);e+fw%tZV%hl!kpbQ8{tMZDl^hLJ$t)e!&bo?`n}3t1a=q!E?!F~3}$pR z{O2-BJAmwOm~8Cq-RRDz$rNW;s*XN!PhSjZM69LhR6J`T}S!1RZ@AdUa4n{}bNno>5%n7w`2{tSrPUk;$^ zdKG_CqweIWARgf(xvGv)FI*xM*^e{ad|lzxl{4w}XMUWXJ@tILczHA3^r26ukAMC{I1|2*R`G?+3mfY= zRsuTgkaO1>8{>HWAr`yXB|muNcsh4rJ{`OMKw87AP%hwe?(8|7HD62nXR#BG1v3}p z8*BU~ChWmt94{V+Hyjlh(=-$@+T;OqfOzbL4-P!PvVlKA;UEj1J@ER&D~s4&U&D_6 zJjfvr4;dSr8~pVXTR1*5Gdqo?J^xB@Fn##pFQh;I=I^JQZoenZAHO$kVF#aMn~x}y z6FdULggB_rc<=;kFYK>%TM=vR)zbCVtUY1laPbuzBf$@dJ*$vapCXwgJDS4zAycHf z&N5kyhqfCeXm_61n!4Pz>3OW@0Jz=i+_1v_%Qni%WL=@1y7V_jphD&`eKfoHS4seJ zK#sqfP(MHbfH*dyJG$Qu)rfoR2Yo}5&uwop#Llm~T9~^z)&K(`hv|*=K2fy5om;%e zu@`}{BTyEcw$SFp;GL3#2(t(Ku;#K#rYM{&=*PWbRAQQ9wXDkU7~}-dh6y`_n(N2v{8r{Nu(rUJs^8cC+)6p zT@hQ_RmUQzE$7we4l3Rz(H?sMph2ui__kbOb0(Byxz_kspxqKZLQ7+jLbaB1Apn+l z4k-K?!uL{wzQUBzl5I^!cmod^ZT)>y`I+BaQuX4&gnZ6 zm*W;Q#EZM$@%6(Llsq_=hRkg=CVq9a8uzhWL) zBwxrIMo2xQWkZ7f&muGq#)wm9@PcfKpa3>n4tt;_V{uPt3u9Bs3A$@NFD+`8mbhzF z0O<3nO53;Hxc=^HVQ!{z;CcfL(V82?`}1h(Awu@RdlBf3Ks!m+BDk(BWt@=TWbAQfhgrm!N?W)GP+#Ktn^(Z_*MXdyxxE{<@7H2W?)WIqIZ z88&5Hyjb`>>>hBKIz>$n-bKnm{|B^Xwj zCROriz^Oj@fXN)%jU7?O7%AGjU;CgO<1$gk1Bh0YteJf__N;C+fr(m3i8>4u2$w4O z5&P(yL7j6NPx{CM0kFiO&OCpq8Fc!)$OL~Fh>PhJ9TD0;Hr%$IsV4{nPZ) zE3c&kH@zp_^LKtXoy7aX58rTeTEdU`EUjSCjRo@*zxRMwAo88&2k-zghx6x0HZP{r z&;2Mp_0*5k$}(PEi1&+6uTG{zSTwKfTTgFZS;dYtJ38z|&WGNml{LL*9DDb8bs`=J z5J&pFvb3BQm+@<#OL(U_Xn3QanZaukvGdIZ`25U1?1ImwshOEHgQtQC?5eNfEcygw zsErX5jVUBz^Onf0Q0N_49P>eVucX_IFN>fI)k9Ngh(8l=al8&m=Ub@rzM45GBy$jpMzMB17DZ}LG zh0z|2S@FI2R9d;~_s*T!)xw<9b99Di;7ywu8cPgk4%99+M0=IJ2)x4)C_}~sYhVFr z{Sm#FUvdJ9!zgHl6Og!o0_g-#BSKmMKn-^XEC9YFaZGw8bGu= zNIo}6U&@mJ(s!pz+QD5{p)A)5&|w{qouJCH-~xl*@OhjwlpOf09#<%{Xk zOE09KKKlK%hF$5CcR!Fm`n4~mnIqTZNXkMw`|R`hF`zYkd2$AiGq~lhVHDuGW?^m< z{FhU@@N#B=iVPtX0wf1UQ>z2j?3=(Aj~YFipP z>|I00c>Br~{1PXQ!=Sv5#W;WaXCD2GXVH($&8G#tlY9;j4cH6Ue1}sq*lEYxUGN&k zbC)ipxABG2(g=!SH|fg=bUj@Gb2@ZtQ!w6TFR?eKjIbLJ}U@%xV4n7;gl zUr#^#!MD>F;P3480kC0Fk2k(>UbX0pHD4HF>lX&H1u4r^R+2S^D=uK`Lf>E=$p4m~OpGe+|~F_KqU^t$AYJM~&7SeD{9tYGE!zIz5OB^E^bFp5#t+=&!-1 z*fZ%Gi-0Zin$9$)h1~%{89g_BjX3n$P%)~HQe@B#u|hZWuIeGQHCQ)6$PQNh8NDKi zMY-bPM0${@Xvkbo6g7(FptqP-*j~P{#OPvN%Z?#23i+9aa{cbkA9jjKEyvmmu?VW| zGVONLrB7N;0~m`1`H7XfcFeIbQS0(y3kBIh8zmnwx7Y0B!p(2ypxq}41GZ<8K@NAg2}R6oMpTQwe?iCc|f2cv$0PEyhtU)X&h)>!=(cq z{_vH82c}li=B3l=@$dabdLCcsJaO-X>DG^aIUT#{PP{|>5Ejc5>GH;M!bx(}`5y8K z98;M}Gbrs}z+sQoi|PCePo|$f@xAocsn^oso8O!6`?cT3&NObxc>Brpk(<)mYtQJc z`5bn$=Kio9|9H?mv*uzWgk{e7Xkfuqels?@rebv9WCw z5A5`#G_kdS@rPY=JS5z8-)C^H{jbuCkN+Uu@sTg2^H(--=ADZ)e&rfZ06j@E3v)9k1o#H>JI2K%Q_4QR?XC)92<=wTHXn#W3yeM4EvoBz~B{j3Yf0+N@r%Qem@ zuKg=)?wB3!4DV69_iC35^Dwb%h6e^h$=3kmk!_*08_&FGPxCPC<)KbfDr$!Dnanqa&fzC3F}^@&*Ox$tL##} zWA$RQ`xs6tDm+p`RbhMFAV;gG)e)nbvhMQ~X}aklKh>DPBGEz0OAIL0YVpSk)fL;Y zF*KUYXc9#dAk%5IA&VRed$1}ezv-&|4-)xE;cO$E`=&qos|5>cH^#thAlzi34qNRp z5C@O23(Vt3v*^40GUwd-Vp@FT)%3(uKTT&}dl`qq@ny)b{SWEneV<56ILxuWiUm8~ zO}GyE{1PU#LNtEXeg?nrx%^gIJpXEX^>zF*=VOnj^~r^F>wOQV&wTC!Y4P%EI(X~t z>GfA%O`DLhfw!?t;q3V|mh#+*p2V*B6lAVrQM`epFw-~&BhO*>48C-_fV1cmIAZb! ze%W*7%2}OZf9*V8aX2}hW)I(x4jwz6j^6g(boYsy(lLA{uxo$pa%XH<*AEyIz`}9Wv&RRTbv&k4@A-=_P>IxmAhFycT$^GdgpZZezcYpXV(uv#N zmu8RLoYwGbp;O?){=H&oy-a~ueDhquxOifFN53n59mr@J6l-`*pJj-g?N;fk`Gass z9$Z8#uA}Ip!;%(d4oBm0g)YqCt+eb10-^rd(L-v&BOXMDZG^MmA{ZU6?W>FnW!Id! z?(&FZdxbaSNVV}qN_0f9(r(j5>HO5G)?UM}<#O8=vJ^gcJtMsKc(NA({-C zySkWM>%Lygi*=K3^_i#aErciu2VOc{JaLVSz!p70Nh&70adIb%ql)7&Y^%qlNNEQX zg|i%W0qaYPsB4nV*l>Z3RQntfgsGT#AEB;LTkAF2dX@+&gSkAfyX{aKnWXYUC&CA=p3$l|Tlr-hYO{SxQoq~3pmv*_Hl<`+z1 z(Bv!(_eC;v>A47hE#d-r8Lv)!p>q(cV}<5xfr zq?v{N_|YO9YMGu*7f+u_YqR^X^F51KFRrB{3pb{l-t+$S(vQE5h5vk7)pui{NEH=IZxxc}qn=YR7T>7)P6@1#{cOz<1C@#~XnQ_6{6 zRChveLS`|BE08r*11Ob2dT5fKU@POmdPaQ>nj)_*K*X_)R)?{ws(rmq)`*?m8*0n@v{izx z)gppg`)Fp?5Y9*DXCLL&y0xo?IR~3HVF~&KesDnv#zqID;WC~MjP5;G>&G77FL(qv z;MsLz5d3Yq%l8tqS8H;TK4|jpt>K31v$9LhI2mD)!V*7ho#DyiOSvZ4#kUNc z16#;0YqgfTJX~zuwnjxhSY`jM+_0-hHxLGoa=0W^LfaO(#g4{TRSK>naGNTbYu?aj z@NJ`tMYWAZQ1YRI(r7alxB3Dm7S4LkWc%Q4Dg5l8#qRdRN?LvUjkI{~OnUarbLq8L zo=*o(+@5az#8+@!BWf>0v6F7T5gI#`p?_r&cW?)*SnU1rR=qfdT$;W=a>_MBn+635X_ z=J%E!+7!5ujkrk({b4YDMurdLB=U|&Ia*r392+~bA6m>LYU`&Sts$_%AN5`2FoTv@ z%&sm9(KeLB@Y{t#-;D_|U`!O0lA9+)Sc&~Tq80hodb`Vod0u{G<6<{W236M4sTe9( zYwRB1UIf^Oto!?Dct=9Qjta>!@+ea`bWYCHhfg)3Q*%v9a*$907+@W5XcAcs00AgT zq{>;6F9d=#aP~`;CW7b8n}s}ed7{1Ip{?NObe}a!rY=T}qV0eM_fGOGbu1=om0`S7 zsuF<0KFp^=;U&bAzL`7^EB&ra;$efq266tZA9c2&H=N>*;OfhmH4S5#jW$GS`aC&e2k0fqt#(oEz^#A zIHM4xci(C-9%=;DstJf#&}SaPELl8Ru3>C4N+>X+X5Hf7Zy#BL2R#Huwk$)bU%uq} z5*5Bfd;*KsS!^gB#HRGZ#Fey!U*>%Fk-te#fA70#;rPk)!Owj)o&4CB(%iwL5Q(3+ z$5E9f{5mHWsVnHnT*Dro!W&f9E~lkeUr$dy@uPI=)RSo+cB}7s;FIaVJrAerZ@4ur zFD~Ku2hV?}w1Qpo8N^1t!d!tpEZCf&n1Plyb6j&VOEiDTXc9U%@HUtg?0kRmAN-H$ z_)Yhug`>yQ)Pd{M|NHO$uXOiYZ@{KQI99@ufd>riz;EIG=Ge>Dmq78hal9{`i)3iw z2RiuhIWdKIyWp)YQ>0}XIUiKiA9v~O(EPBJRq!=&lYGEE-U%6XZnpLe@tf;)GiUq${+Ka$8 z5zuhg!YL+$(%ZIS9Rtl?S>&;%eyT+QCs5pdBgF`fdX@WBTW&Bu45H6V11|#L;wqW( zqrH^8>+bIcuH+9bf|*|oGa0<0w@Eh#fw~x{qfbL7PVzF1AkpL;bS_a2)f2n;aWQF` zYG}b0?{v&aXolSc=z}WWM#NJt{qFHET!VnPiOAL={(9q>QfhMj);g@%ht~mZqk<$^ zSr}?T8(I$8Mi73Q`dS~$jeS`nm3RFkEf#oHprVb8 zvX3VV1aDU!ah0&p*h@!B(&B@&(oCy{Nq{klr zVLJWl+v%OV*<#FeKdXI%l{NR?6;)3{fEvftvU z3>UkYW0>=NI<4bt6&dSI>~izVnEZuNKA3US`c0fAf8aO%G0wJs3O_tFomTLcmFYvr z(kDOq$@HVY{FC&+r~WQnz7e+aF9Xnk-FW?~Fc*G&$2p{}Z}A6#Aa%=r6lj9)RL6px zHZq+2+qc>pIa`5tGI0l;$Hq2b8zZ`mV=p({{o!;y-r4>Vz6g8ULtjo87WryNpU=dF z(McgDLhV}jDUmqR`mqxlV&s+dv~3&?nm|Jab@!{N`=7Mb9l4*SA4=zk@XDs(QpG&s zwYHP`a{z;W2YI7s8@{5~+ieJaH|DHIKgld|1hpalXS!>$K>s_v%L7^BX(e7Q9&D+C zy&AW6wJ@hb_P07kgLa3BwN02^bL^G(A~1dgMo!vgWQ;Gcp|t+VsfD6K9B-Z4?h&nH zI+ILr>b{2x^B9~Q3q_VGjJifLD@aBK|h z*BQtmOKdKrLlSD0n_SrKzGNf?IE*)H7EN4*$i$^!i}F&BQrM+Blu@7F9uWk{881c7 zRn(dq{)(q91_yH{bAWbJU$iH|hA}GnH4p&9?fzOl7}6r>vAH&E?8eo6kiG1T>S$MQ zMIE=IJLFLpKVf6&3)|5D9pqL8W6p`Mb$FM{_>lz(?@&O1EW+X!d)#=nAbw~Euqo6J zVh4GFzt)M}^7C)KlAeA3XX*7*&!w3|H>Q*C`CPi^SMN=Sj$M~dzx*72JZKJYHCe}* zYrM$>TjPA+_}1ofTD$Z{dh^xi(&=Y@p3c1dQd*hXm+txaucq7Ye<)4vJB+r$mm_h! zWdpBCT;0H1Rq#5(S)6HKf&NwOy04%-3j?Q+^UIQq9RIe!zc|x(+CIfyYp%jk0Di{e$vDFfod8_#yku597i$8&mWJ|LENQ z-d_G}51S+uRw)<|Edi#fmKAs!H;An%--$B)3N7$mMH>q||Lvwqk?d;Pxz=A(8k~@j zTvOI%9RU)6lpPt;6)=}XBVez6K`*2SO)c5V`jRP&nVq!ZXYhp_G#2O-5u{wANtbg(jn|{Z{o|5%Q!Rs*6K38 z7>QrR#LoCpyp3fIzlgcK_;xyT>Z$blvyY}X-gqM&yzw^drr(nu`0y8T{(Avux^GYG z_|cwKEXeIkrW0un3sWw5HnAY*ZaDiD_RV1t41*i;0I}hmKzk?o^h!UGO+fbsj*zV4 zhlW-+czlHW{cKyj9R^?MoSHq5CO7cQofF)hUebbjWoiKn!b#9d{du>?2%|;~Y75;`va^rMX_?2i<%y&=35uEPJg_ZE|7Gx5Lm(vs(R+e(`9_ z3LXIFkKd9${+Z9GAAakP)35yI@8Yd8cxSq;;-!OZb})x=pr7NVn6Io^6|n(EO4Ga7 zp`pwvB%^Q0GzeI)5@9CPfZcjU8pW_$Ip|wmLDip!c*PI>vCk7kncLBzR?Hf4Bx%1^`7zvDwwj(qZ-u2)ecbhZ?x`=*P|7!tY*15As$))2;&&#hZ^tSiru))9#DN@mH$ zg*K=RTJG4(_JCDdWUCE!QVA1&R%DPRX$EpRgorf?nW6QTH+@S1uVO6_Vw-8q*0^aC zY(1c|K>GaVH4s_)P688{Z*G7YgqENLy|ThhDvM#~1GeT6kIN|W0R=^|J+P25y)2Ux z2optxNa4HH$~A~+5I#{A0cBKnkc1#g18um;NQgD_=7Os!^@y6JKp0~{yNgd!X?)I> zEzkbDSY?idouv^ohZE8Vrno{M=~1Tz`YQ@h{F)-ad%^xn^t61$5fJnMz0waWu?_5o z^A(2&@K%*s{95G}zNC5X#h>FxfPR?HU0h7F`;Mm%e){X_*qt9tllT=(p8LLl9o}`k zS!D(b_(M2ry?=f(UBbJ>Up;jpoqF~W?2zNvJa4`yoxJxm>CRudJsrfk?q&R_&*e)? z_`)O3d}HC7Hn>RUY%!z7IDhbmi{J_DjBnu`-xFBC^0zUkxj^SRY}m*hZC|Q`5I%sL zn*oH;$7virnZ=P4F7SEAd;&f#FJHorJkGV_*E~rCklvHNgdaFMe*KNqo6hzWKH%y#F41=JwU zv7ta3SyrDOtj1$zmqfQ924q*;8vMc$e}KcYnRE)V1P?^ ziy)3Ec&%0dfzR-&AiL>bLnLe51|VE8p~bG6f%fC^jHI-$qNgga$_b*G$Q`npIl&1s zj1v*4`TW)~5`cF>a)0|QwJ6iOnuz7P-p9E;Y*!0&v?4TwdHu@RfYwN$W5c5!joZ($ z2i=Rn_7Tto0y6F&l~l^?mAa<`$^O<(T;#B+W&A-zdXts4xKP6gtwE~EUcee-3&*V1 z$aS^xU{bT}3U5TbR`N*5swTdnT!>DGhhlQ-;!uYu?x5N3Qzo=RM@|1Yu4ICXENq8h zt+8#Ju*J}35Nb3vrMoFBAgN-^H-OM38eA%N>gHb$$VTZzssEr*^UF%YBqJ;)l&cv6 zad)9Jin)jYE_8%QIuucUiH}QkQtbu<#9VejMr#M}Vy#^14pH;*Gw1zqgoHCoL zD9pA&eLxaLc6`E5%A>7}W0o8u$dZBJkuuT)k%gfW1C!t?Nh%hKT$)O$aQw>;A8Of# z*n!33mipO0dFGnx7%iT6p1|T0KYxQIF%FY7g{OgNIl zqbVCaf`Xh60avi&f74y>Pp4meF+Km-kJ6p@e>z>{n^I^V;zO-mjPpAwv&g{(2=F?4 z4jgva*YQ@G3A{U<8u(S=w>tv#~P(tA=Hj4agU$SgEQ zVh|(PvK@6=VHXzKtn7P!;+FfQ>BE_qm?avjT^ z@kuO-58zIAVC`aBI(s@TUw9KoOoxdp?*x_wm1*jvTugXTfLE#nlUG zad|l%T*EJO&f!OVHW$;{#W&N1*IrCdKmTNU`MKxQ!gV*Kg`0NqXf_w^JuGG;F(7+v!V;v7JcD#F5tC%LD&Bag^&@p}nB{`0Xf{CfJ2-~7MRP53p^ zg~NAXfxLov;!C1Xzcjg~H?#1}dDzPLrt_hE1IJb-@IYX@_mBw!#+hf#wZH&%J{Xy z&+hGW7^HGyvJM}E*KBf_*b49V`oKqWV^=1*ULW8myjm@3tv8I;nTghpRya`!jRjeN zhTRkcuX|rqx;f=@;;!|eyq!$k8X$>QBC_tw_B^g!p#~HGqpaBF_yFMY8dJriO9q=> zaBD&{IcjI}leCfJypD^rwNx8@!8lI zo=uNG{gd?66HnkrZ*EQRec%)6-g_TT3pmy>Gdqui<`d~6j;c&!hxZV6t5>l&Uq1C{ zdh^`rbo%+H)1`}-)1ez~Nyl%$FMaqEzn%``*D-OZJe_*taU69ygd;2XHO_S$F`1sh zj`%DV%xocBNYh`nC2}p(tpVkW+SHm5>s&7|A}I2_y?Qg~gT-QQUTYx^g!&oir?D_! zB_R@D^QeU~<7ML1UewQ>^-K7I=nXgAoF07eq4bl#{`2(7ul%<MN&JzPVa{&bJhJHLT2+gCAO)VSA+tMg$|j~%MRiO*;`>= z8_-1qt?>&zK-L#jFnqzP975)i4V+WeCUo`!nNR8Dz)8swyT9X6`OITWCLP| zaYMknabS=4moEYu7U*~}@HNRO3_e`cDWhlN2G7Y8DdrXHqowX>(eOg*@w#g0$gIw7 z^*nI}TI9SKUmh!{5<>d0HCU7jS?nFxK>MOyXlo?yxWa|(U`*l`SH#g&P09t-U+Md6Q2{>7SWZ5d)z?92> zj-wpOis&(r-hd22^a0byc-p_vp$JPD1)5EiVB%c~mv<;yNXre;)r6=}OY4L(1Z9LA z+Cw?!t=Uat5A>6=uH7dZT8WnFIs;V~6^lL`+@tl_iFr6G=v%mBVH zH;Etc!8z;Yi+H`^^Xb&{kEPQuy^!V?j;34R_o4LQ-5kDr!J`7)E$8b7nLNw8-u#n3hyb?*DNB|j zX8eLE>9{=wDmmdE5t!WWos^vS;*@o-%iyV+a z*Kp;fN}@)&n^^zLFHcr$0>4N6B(G3`j7;B;q0tyV9GtA^gwYD>0q^Qfr;V>auti_P#TmNfpab|wux8*WK7na3G zEFe5+2nxJ(1Wo8bPEhTWP-Lp+M8V!gm+(KE%r=ccpim+NpxRpwXY#8l>}p{i2Dj~l zS_?3{as&4vug3H}o?p5Mpu@4x*|@ODkiE(JVd+<~5V6UFH&b!tIg)X+$&{r!nIukh zF`xE#=FnyIwS=&yg+%zc`Kd8 zuV_B~==aja^Jmk-p_A#vT^~yKeeHK~+~npo^Z&E=razt}=bhhE_t90|)z|bf)6>V? z!3=uz(E+@CDclVP)BXY+$w6T^ZVy zv@2O6MXo4`$C5)*LyqR^>6yNds_w4)^7r$7A~N&$tE!&qX+q+Vl~upYh$o&qA|s!S zjEo%7{AG>8Ztx|{#ktr}AU?ElTOa*-G2YaC@$+w-i-nPiIId5{KmAkxMcjAnWbEBD z7B6b$kcs^#G^)%R-CEB@%HyAQDhYu(d0By84tJ?3eK)ii>gg<|%h6*zMX;IyIk|Kh ze3`plH~d?F6-r)chI{l~$V@H4H!I7UMx&Qmy`qom!~D33D8W8T61b1(CWg`aRozUi z%OF;QS<=nT%!7}|(@%aNzVoF&jAuUc%Q5ddUX2COFfZJzH;K-5uA2z>qnD>$k9`liJ_>sq&cV|Y3~@zcK;fABB=S$s|}-j44(sizRUP@1^# ztsCZNJ0QRZW9w$w0@wDu6(b?wy%kPNo3^IRVr*OZwo^OF^pqWYvzHZ;><8DKGR;ZQ zi`Sb|4bix&nlNx$JMpPrtEsLJHvNKbFSjPw3EGpUa)K?bq6*wjbc@^7q4I8YfQ8P} zcjMMt-_hPc8`bNb#vR*Ibr{?^c~60PRz#yok<`g|lC?@{H1!0#vueAgyB_FmQna1! z%O}|Z?R)~z{I(4`RnT-89VauV)18o5a-pc(+v+nRXjG|*4ik5q72fg(Bl)*wDy^c+ zG0R$=xbRezk`PJyBjJ^UOKYZ(5RNBR3AvO>KY>{Ib#*8pU&5qBx1-*wYZ1~MIN!-6 z@9q5Fpx$MIi$;~dm2YhwRerUrS4i6z)$*2S52n;_{Y$3b54F7Gox;j>RoQo#_JHi8 zu58kYk@M1xVCpv2VOEmJvcGjqYOqPU_DU@jP@yqKrR$+e-$Chkz>KT(3_@XnOfI?Y zlbFOw`MGbjQIT7nf`N4fyo}l^jME6Url@F{!ck4pSf9HVw=bTJ*Ux@0u3f$u!;@37 zIdNY+`Sef5u}7a$dY?z3hlM-1b~D!IXJg^=xp?EdUytuT|8;%fXMao|c`%Njd_10g z-_OL5 zcqErgR5Cc?dP*1JFKWL2(;xha_<#TDf3G?855@iO z`EblBP!rg8Z=jqurq1y7Pi_o`G#$luN*VNFPCFy!4z0L5x02FLvXNR}rsQT$;XB-7 zUTaW3aN<Uv8zCrN8($*emcpjzH8+cJ{(}PRZxn1*Y5ib z>d(J(!|p9GS4H#3>D6vo8C?iv*}hfQU(gcnrglBBtp{jdwU6mX=xu$8S38KrEu8}y zwLO}jA${#I+i>Zm871hDC7*WB(r0yR^ePv}!o3vtGpRy>*D`d@^9Wegqw?q2+EeM% zPDVGO%&@3S0a50&{}r7KO5PN;c7j#8CaT)>T(PNjDe3PFcT3X$Cg6Nd2a@B}!4@+Sr#?;^5A}Vz0}}@!*kTF}`Oq7Ur+S{Edro<@{@!JN-gjc>T>7ojDNuW@h64 zN1ux`AO3XgyZ>Ry9`$GES2k8+osn9mcSv7GyoVJmx9j}NQrpcjZQoG&hTej<|7B-8 z=3T-_8Ap(+@BXMvSfV39Wn`SW=>DF8O0eH+rU*aFGbr^Eis@9>4Lyo|siW6ow|h0W zeN@5un&yLR1sGobWIBy~svC<994Q%YDGI7X>y;0%qKa=WBu|m9Ai5FJS5EitjrV>0 z$K$X5{lAT0did$s)RTryt=Z0!ldGx+0%b32$Blsew5btq2@o}PDBik>Ax|)`jC?mH zJlRKG>Q6Jry~iuO6bi3l={l>m%^!T8QLqRi?)5*x>fyK z6!dBIa9hi=Ude*dkgDA+xl^f+EU!3S;zn5#Tb1Tqrk%36-kc&`5<4AK-s9_vUymJ8 z*y!_qJEj$XCpkZ$@E*0RU9z*+#T6@$rRCg{MfVh#x9TW`vfYimz!k^E1*h8AMc+;D zdf=USAUhk{UTJByDVDa^b}HS}oh8*a3zko^4*R6+V$^oeMRKRD;&kaMZo(3y;?=S| z=0!%DG~^_NtE(b?d<9e9oQ8d@x)-X}6QVV7<=g!Qs1x$WsqDW&U0 ztE*>)ZOd+3pez$Z=(2Nc^&*AcT`jo2F**p)azO&2=fatnG&{m&1*dpMxg9tAxu{U< zF7+xToeC>xjegE+*X7Y8sB1`<*nyKUp%qS**R+cwAx)`-!flfllUaNt4rDrY~=@I1DB;f1AXprdaIJNbdNsLbCN63aY;mXJ36G7Oq^5YgcAt z?}^9bfhRs1kALE%Ufw(sWBSO?;L4)b_M3}+tC!=(tFOhJa9{q`H{;fvmOIqw?=el$ z_~M!xN`IPF$qi)6&4pM#NO#8PE%$8!@JrK(+LYf>GTF zNE##FELrIX0g_9w<&O}dNvfM#NGRUvp0h}Yo{uSwZs_e(O%qzvJojfm_EYiAFaA+{ z{DCK8ePoZGNaPynt9mNHy6%!LUGRI_*jlQT>JPc?MyAC8UUl%fFJ#wuFHuW=rxfBE z-aSI^MAcKa6IHgsol291tBw-Ki*Sd?k4PxL#l1(bb(RYHdI55 zS2K67@Gix!2YNiIZ9~@wYC69TybHUVAibe; zL@9mvFRFAkqP5t518uogPMng*wnF6AiBDG)lgzsTV7X zAaCopu4yf|v@GI`lo*#D`p%{5opEZZE~U{E-*99_k#+;4o6iygba1_9xIrBp94e;i z+qBhOdMYaU0{C$+&szwpUl# z(5g1v{PP@yFnV3}Eq2=AEk7a}JMcx_c$x^3rX_7E=qAu#*3_Y+x+!?$d*640Pc1>AoQ$=&E=UBz9`HEBFHNVpldFEei zOHFpde*5r#eW2{>RIN(!XbVN?6tnI*Q4#Orcu#@3PaP>x6?=PfU3G5{br);b19+gc_-$KS zZG7$wvb%xAon1Xs0KCnuvO^{p1Ujq+29KI?heoCXDD2DtkcQc3qkF};$g?7Mb%l<85npcVdEmtxl= zFI{Q5qD?8L(~IHjn*pW4 z_tziw)lIe$U4aRRCl#0ttn)q{=LZC`4HoAKSALgjLkjfrUc zfZ23l*bP8wf@so!4K+eJTWVJ5=t9^8FBZ`faZuh1Qw7{@Rq-+a;F=S ztXGcMkW*ULR3Wv#K}6cdfWBs`4-T#B$;I@(`?a?CPsSI2?_b2H{lEarZs)&{&@MywRrv9h1j!4-qf_2jWw-|Bb`>n)l*-jTb<`#Wg!rs5_Yf3 z4r*aUw97+d@#M!o7r*=4|5Kbg`EZOJcr0#fUHLI_>JiyW?^0#zy;oCEXtq0GXXV<+*$z># z#;&47%~$!5RUQS!I#(Fw#x7h z0tV?|jbU7DH*Hr?x2O+J)*vxSn@rv8JWZkGnu{d_;5S&EL}k_iIaz~t)3P&SKh|C4 zdpMMSrKb)$+_OK)g2{Iz)P#?`oT>7qY*zxVL5xc`w4#Ct#WpT<6Y(tc@C z>wzrtwarU$_W7^GU;oMVxN!M$Y-&FB;rma;;YZ#Z4}Dx;>O6QfmNY$OUP1ei=1;Sx z_>z`0T+*CzbU}6ouJ|ZN!3B0uyH3E&3y=y98QE`4s|nkp3R_9c&RmP~>w|eWP&S zjS@`9)UAEJ{Pg8a4pl!jeI9=K< ztWHx)LY3ICF8134Bw>YW5sg1A%JY9B_6_(lg3J1Z0aeCH^ zL!O*T2;6+hD#1unw((>HO<`FSta@-EPaf^DpuDX^hi^wfCn>hdbvlx&zuAK*@#WBf zNK~y1WLKDC8n)*c;})z2eM_Ry_Xyj9bZ>y#VfNT4mQ_pFAN3<8xZvLHR^14ny47^8 z<4%oK6#J{Wz&FrLq^`6F@!Uxz6;(N1a%)j{K6Xo$7S9&kri?{a@_bGe!Tmk~1y8wH zy!hItB5}erkNi$57}x8AF`#KDi`Op4#q%%6`R_d+Z~VvV$=xyxL#u9rBQ6#R4~f=2q0nGR=jJdHd$MaiL-3 zf$n)U^(4JS-smy>`7j`|ATe1z$lE}Wu z#QjA(=Ow~z$x7^u)>DG*0@&X@1-+TtHv%=qU8rWu(W!3TH2kW+YWYq_@a{tHPHg`6 z;pyX)n+9Zm-7%&lIp4U6-C#c7JsNvYfq891sNPXiN_8rdoGY^zmWJ-`cRg@-9;lt( z+wBh;?zQ=G(I{JXgxNl~bTC4zFyn~^+1b*2TRZLD@J1;S3l~YI>B23W%OqQS!S;y^ zTXnY3dg~XH&^zvXvSsb1=srWITY@kGnikezlt zy6%K@m+-W)=ul`;hiXS}2R5Y*WLcGPN19T3{GsxV~IG8+F@ zlx<|Uby8ZOaMHITGQM^eiazjVW8c;X@}NP!h|y2I5>Kv|704 zii?i|t;O3nV)3%Rx_S2Hc;mHK6?khd@t&zT^Xv!X=}-T>M!1*a@PYlYwlEv7f8~$j z>?<#6Rh2hmQOltmdEjJ>9eXr>;>UhAPU%aXH^Dvrpg%~%REq`um^Pt_5HZ3EAv#rt zhQ}NTE-4BfCRo+dAOq5m(0fq99BZxQrk6@JazCUIbtX~pVj^R)Obg-1S(heacc}v% z{gEHH2^^)OEEZgH6ty|P1F=!ByMTefVqz8e1{UfY1|Ah5>2wXML)ZBhyQUwH@L z1Y4Aej*l%7)>0Q->-icfR-+Hg7j7zGrhaqF;2zzw3lW~^m{MZP<;V>JU)|g+Z7jv; z-b3-h4}U_dwETcjiSHsjjzOm z@BOrvp4_jtk!{S&U1kF+Hp)vC)0N7=L^eOBRZt*q$j-s0k7tmqw^{w8&|rruLvUWK7uEt_zhx#ncS~A}>cM zJdcD{pQVAty^>sL2(7D~RtN1Rlb_-?2bfGt@kkGFjff73*Mx$}2?fVP`r798^JioB z`o&mTxD^-9zY+5b^KoGBbR0W*Dn6xPd_bSPzozo>vB9|T;@9Kci!a30Yjd$OG#Q6a zoQ`7;e=tsc;_t_#rZViC*%#+uJFDPsT+#mCSh%*J=@opmM8xq+MQj5_xSb*TfYgd0H5LAK5EeGA0)60B)SYs0qUH#f+sbjNA;=s z__~{bcK_f)tlyrEYl1z%M~S}n`Iui`iOChcpgSyHqH{YN(rj|+zo{2Txw#mczabwi zNiT)_%;P86wllb4KoGcmv?#Ppquw>py79g#lFdWDNf^@8kF}V(?^Hbe*i-TCzx<usR8q3L+u)i zyk!^4DI(~z-%FM$2WD_g-aE1A_QyNn*bjwGo&oiiW=D#Q8O0f6H?y32ZHGRPE#4xoxjEy>ho6mlTxWoSRi2cc7{FcWUoF2Ikqx<9;_1AE~gS zPMR z`Gpo%PUe_UrD%@?AwmI1I}UAs*AN$U^0roZnJxu}c8zLzfNAE|(W;oXrO%5&a!QGL zez&~eMk*PjoNcM{eeSC)E!&Qd3b**K;MpmJ7>ob-JG-B z!_2~Sz$iLT6JP|{Qw3B53i>t)7~x5|;1gmrrq9j~YYM~K(n8$6aV5^4dr{LDF2&{Z zXJd6`DV}`h>3Hw^o{T+O9VIqK;)d29f92cysLxlw8ZWWn# zp3;{d55%&^Ax)cNI&Z?q#A@lVR^tr z*V+aV43n>uFoL&!)Wzn&xIRXtPqc8CCDaVhKeXA z{sNDmpA>-Ly7TaWTH;VV_|ymE5B{J3BcAxNmlUW!p^qSO8^ii9_(8l7U@08yR{bV~ zEn5qN79KloX5A^QeFSiRsWUa`GcJKDuypaw)|E$NKvd<`H2F&7;zTgG;xt4;Xf_IS zt9lhy23mFnw5XNjZ5?~a($S5lt!3AFu8hLAOW5FafxY4z{{aTg%zJTu@N-hSF|lhv zo38EQ#*G_s?)BGWuLASQJ=2)hBHg8-_ZFB-Y}QYsxisnM(n(h1e8S$PlDp_X;vT3c zb=%DBkfv{NE(^2k-s&&2;L4%R>2BNHie4aZ3uz0rFJ3?`&(fMxv|gmE?!JiBlLHc; zKWR%cmPN|e$LVI<6mPY*@=e;h#|~XoMOR5ne=NAp*v8D1RjDd>Xc7lRew9nb>>Te< z1X<M9* z7?m;#2_91u6fQxfy#gQODM)walA#WhI7cg)mcnSHVEWM{`P}`m0`YMjM>dyZ>DFA# zU%wEqzwv6^zVv!*F3rdI#AF;idMqAz^h}&NaV)M~y`m4&tj3F9`%28-(#ViLdNXm~ zeR1s6={Wt#pNpq{@jx6t{YXseGwkQjz7c!%<;_LuvdFX)(G6*AmD$8YS}S{ObV9+K zEW<8>I7bUV-(XrkSj0HJXvJ(O9~nI{W6I$z>aHLMz*hX&(Rbt?P07_A7Bq zUre3VP1S45w`2L{%~-iLuN$J3xUCmGZ)sl4kOKPsduHO20{uzV$HcHkt2GCFQ}x3A z3TA-ARG?y#2DSBMLqBenFvtFpuPMhTBnYex@R54Va%0PJMijx0hu!8?}@4j{8cp9a1X?wQ5gNk9`y{YH<3>obAc6tkCKjjNYm)9Cn;0=NrW&QR-#tNxEF5I=qY0Zl{Ey5t&Z zy><0+ysi)S+_-ToRyFP9;F(9_@Kev|3!KMeQlsX>0p40S}g&3GPpyP5Z zYeal?bwg{6Ya|vQ(dKAdx{JVLO!8RsXf;kQb_*|4Obe0f%BNQ|ENa?|;9EBg29H02 z$+{^IKE-TyOeW2yCyzXEvb1;~qWwcit z&jmf{jY1WYgY4?6at9U-8YZDLj5=FAM_QkC3g1Bz_V!$loRVpj zNgwo}h%RYUsLT2h-hn=U7bLX0##AM$j}u5q8aAzS@W$+%-f_RF{_OKH8`XU>X47tTP6a_Jzqi{ElW=#x>w#Vm(7(CGwau;<;cezP$$Z+~BcD5o>zvjt zn&g!}x`p+mP^kVeRzD7fj`ObaZ90k`deDi6V8ae>@N**JS(q@h=W@g*%pxcAbq^m& zE{gBe`PXu&w!<-R3R5jo4Nub9H=rvBMP~{e6p`3wtHIqiMRx*Og|e&a*iVhv3f-BD zHQk{y>qWdv-S|g_He$NY5HeCJOJ6AA5zL&rLMekEjL1^JlorRhAZz|G(^i<-JA%Iy zDGjbFsJ?a8qv98}D$1?PugB~guf*+(=VL;ju%FOMDElTAoGEC&d}S_PJAW+4!ZKBKF!wY886qUzXL8b1y@PEh0;gWg3*+inj>9d=m=KiVFs9RtLdq z0zSa%m?v0UH|TQJ7Z}3Aqq!w&Gf%-hoi3T1h0jdz3j)4?OX_58nzBk}Tm<+gjvEUF z*sAkUjbiVe-mkg)6Iz)?w;WA%5>S{)afE z4@XTNJ`+oNx?-RDQ4oTKtOGK(Zl!KjP}@<(aUGjDWoGXNbo&ieU8Wr(X>!#1mO>1?f%(qOytaT&&B?+$ z*md`s?^68ec)$%O+Y)Xd9e|5h8%TaZq6SitalRk?6XrY3YBOljSiGqh`>x00>=k`Ib2etrzZ$o$XhoEz+i~mmO$BEP ztcEm?c=~YM9-WTm;r%f-c}O3WIUdIj9#NpSKTd0|f!Tq=)c^9aNhcx!$$Yml?*2P5xZ>ztQJ$;mE)q73CQH0xO4v`1zUxbyGuLHT@? zkFS;Xfx1uv8jen6c@kz_@niu0@NVQ8zw${QP!jN4hhz$4VX8#-n4Y9>E;#qFA?vKR zV-uJtc|w}}xN&Nw1NX=>{4qxVh@@WiP{JE@;Ap9EmEpBfB{b(+Fyszu)SC}wK{&{b zkRE&G`vHXfk|jNdH4Ws@zG=-n551VXFJ@=;5}HQAm$hd6sDg6NJ46qv{`hT3*t!&8 z`iyP%-JSSp?mc$};PSF5E5e{Rzcr22^8#$0Y5@#?=uq0O@T{i-Z-Br{UhGo7j+|d> zx|>+h&A|TS$Kykv{l)mpFaEpu^ndb?)SlFiz*JkZYIvaN4i9{{-DVwjDysw_i2?QO z+5}%P{=7BxnE={61xSpB4%>b2fU3}oBwI*9G)wOapmPA+$ZGMeS3kT%cBf=xLmv`) zvbkJI*f&~r>c&VeX{~7RvT*CZ_^yj*^^1@cjb#1=>>_UE_Ja$tQK??jo@I-DDlo;P zW8-n@(uH{Wl{eyZKYuIM^pbNa;&|8FJq705N#jvht9-X6IEj;@3*C;#E^OBWT@TP+ z=wLX}YsX*9w`)s=l>#@p&WTm&dXL+2L1tocXxeTUD7dZ}+9e*Rs70Be!YFPKTHv;@ zRJ4jPvPiq^8eTneJ8VnR7tPWcx>FQ9ZGf;6SG`hhm6Y;zfwpllY|mZez^IaDG5b4i z*4s4NiM;Lpw;?OMtp|aY{u;YMr@Ws>)wR$jSCD!D32yeie1+}J6x~Xw1qk)VvE^H# z6d5W&uUJ`7NeVJHWil=E1yq3;^aNW3v}+22)-+1VGI{i?L;8&=_};_(aIFr){P6X8 z1>CQGBQELl^^Art=(G2?H8*)tqc;OuHgHZ~l(}+qIS$@`EcTv!I*up+pE-0i78Y0I z!6%-M0llC(tflb=m}4xZ#}qJKocm@hE9w|r*2M)|G!j0bB?=kMW_1&_45c-yi&46Q zWZOySw<$y=9M!UTahu`=1~r1M;9Y_8x*Vh1AWaP#j8z5c;IJGaOIK1St`*&^jcT6u zFi%ZHKf)9i$=TGr@_~_2-ONd=G9u1Lm(WqA3e4B_KEash{m*Mv32UWx6BG+6EF;)7DAi$U-%O5oH#2vv>5VkHpI_eMc+$JRkQx`Qf;! z={U$|D=Hw$x*3)Vr5+`X^wg_pYeKW=*;T!KM48Uku8E#nQq+fn5=b&ti(i9K}i-Bx}NR;!A^IBd(7T(&zmwn4Q&x^Q#UuHOWoBVELI zNg6xq8PKD$3aX9R_U5If`0xJPe-t18_{ZYW#~+KU`f9F8wbR`5?Ht|1u0M0{fw@F> z8$?^7y}AH*J8-2{lls-!&F^|(s|RRFbkn|Ym(GL>i(651(cDT0#I3N~6rt~}hraPQxACNO#nH;YN6>}*LRrJOB2^uV`nZD&@_9iyn5sQQbCxi#dB0w&{n zOdcpB;u2NVvPPmEP?_i=s(jKxl)pT8kzMf`>fVHlEv(2a#a%L`t+&v2>h$a?NGooy z6vHzpDI=~bPt&XJy#)0Fj~f1=Vrr2xiY|QE#Uo*fC8h)wj9g|YgT~~FFyoq)Ey-|# z@KG_f_+p*-;ZJ1;#n@k>>)?C~zOrNbZW}Mq@1YdbrUetlVm3Acz-Pdz_a+Meb|Du%|LSSHV>p!; zFF7)DY&}(0G%oF)Tq)53_qg42{YJ(HtaQ~qZ0H@??R0Ts*0wSs8sfHrVz zSc9BK&pGHaJg#!u4mN7Glb^2OKH3ta@$0Hnmg*cD9FJ!|`q}vNfBUcEa}PckLlcK> z3v1CYiwB-#xkda(6M=^X<2mZY{t-+Y-t6taGM}Ye_wEa2W7dN9V!5S%G+CA*p$f#; zY!zRo*mhuUpE|lq0k9QPY^dc~(A|qECU-WI+02fio${LWs!KJ~cdYB2_klAjAM)3` z;SmfH6vH^^mnmo8atBZFskb!hZm8dq{oe{g>RN`82J+I-)mkH^e?$Mi{c z%}dq?e}=WP%Ehy<#-!#GPiUITttE}VE4Ug~;J>K(;nPfHmIjW$s0aNsRyw3*_!I{b zT1mUKg%oM;ZI*KU=OKMWnzG{Tq)@)I(H^NMEN64OG-m;8JJb;tZim#4(#lDpq#;RQ zlc5@WaEz2wR4N{b@DVkou&-$$nxEr7S7Jd#l#vtP(^5G1kcM6`v60e2Y4l(No-co< z3=1mK@B?zqtLOktAua5%0$TA@(2Sp0FC5tVn!;MWxH+tfTHy(Kkxw029f3B~mNy2O zs6_izJ*(Xl01_C2!^$nJio-3@hL^8Ypse(&Ucy{m9gKx}jh3&eZECs9bq&$4t4*>_ zycH0Cwlzw|JXMo|UdDa6U&I7|-%(<0Y)@PfpI82Y)fAnM=KYb?t`fGm{@A>4f z#0|C0QE59SBUa2HrMNLr*YGaqE>CP4XA0cPDf;1nveo{u<`cIqukfu$)ncu%T4?8I zy8vV^!XeDB3T=^>SgMFh`aD-{BsaZ6)+SJzX=)ZOIYmIq3wY;k#! z>$aiZ&Lef|mT&D*h11_*l%A&c;g!BYtAL_IO1b+zx+2PQbcAh{*rzi<%kC;|DJreZZ4t6_>(`ZH%d4y# z#7e+1^-rD+<3#AEOLX-)HW*Ur($Wx8<)^_x%%Gc6k7O~Qm!O>@Xyi1!K~T=diXtTx zbRNX4Bc>eixLrYx<%+LJkjqPLd@*xFQwQjnb?pdm0=koiP`UWzczd3k@FqxQlp7vk)+s8K)t5e@r4(;>=hvGi3#A@) zCWQzRr2}9na{f1jq889kA|#W!;{>dUNlVkntO(6Rc=g(Z9zZ!-0~ps0e)_YY#Lsi) zRfec!kjRZ6}_V{to*QU5C`;G*Zos_ z#7lv_zV?Z~&=+UY9P^r)s_^28|kdo*T+o>6X6X2w9goiU}8< z6;5&bqJa98&-N}x9_%Q3RG#(N72TKiO;5+3My>^GS551u>&tJou^-_FH7^76zg4i* zSJth-s>-p6sex~Q`-O{|diaVU<9lcJKChtsw+j(tId`?)Q(*2!PQ`R92c>J{px#QE zLC$xP?@HHQPp*e%4m-bk`lGCD%s%FbQjQ} zNxmIG!K>&UmJL!pJrGjynnEX1tBU7bMHIs>PtL)L*YfCEMY)Ip*^l_5@R#)F;?NRj zs*S{)&nYa>=~s2>&@?)zIJtw?hgb@>TpT-my2<3(^6&+y&9>a(+XHWHq^K4%4Fa9~ z*j;d@8Pp&g-!Rr#FGTqwA=4oUwguNOH}UXrvZe~~GMp~MIaNi>r0}3d8VNl2DA3V@ zb*!(Uxz}Y>TGIw@U%3$H-#DvaS5sFsb>P~Y=i=t2^IEg|dd#ab#-1_(6V=KL{N058(uX51ATB;#B68dt!}!mu|*~4y0kCb~3EUeTAg* z1c%fwK4GJMWh#SI=qUH-GU-yNqykBpn5v;6OU+oOQ;7qdT5k1+CwXB@C$ljI0`yBj zgYTCuEili9QfwX=QgtTigImgLZLz-SphOLb8zCvt*|ZO89Zn~+t;PtFc#YVRCpR z4rmpgVLhQ4(F?od<0D%Ag}_(sNW<|gwKy_7=9@`1AF-E|;tW4g^iv5Ok4c)tN9 z@;!r;3A-v0A9&NDkz$>T;#F|xSoMU!^|AwME2D?lG?dk8DXCQlrzWja(W)y0kn(tk zX+7SQw=S96x>q?q?ccV8H8@63n%aI`2B@n~LvR;~9eK#Rlb!T<)M=&PyF96zx?Wd^J~BMYa_q$8^6KqEjEZcsD{1e?w$g3xLaaJ zU&92fjfMIsee_!@vx~6nf$cot=Ec>4A6I(Xy8rWHyd6zHEO~g}Q`7?~onF(K^k8nE zNxs{c0WB;O0rhuARmt|H%|(QA1Qs+OIJp`ln#5KqDuRz~EA-IWrNVYuW!xME_b41k zh!jLr+73@%eK`TsJi`$9S`YNuW0Y1p9mCciN6*>u{qp(`K}t9|$b zIpRmKVi9t>gE?dgK>P4bjHYPhR)h+!f1#ATQ$>P!P|y7Mz>QZIp)w)R25UCAJ>608 z5P~T~aPtp5Bf&usNR{%cEV7BM6g$C<12~3hPXLcymX^R$NT$^x+kOQl+~x2BRX?D} zwFb&NB&UoLgIj(mF(5xNorH06d}_MFD1DNupxv65W4X4Xrz>kqF)rN6(J_5AXe^E& zIS_j%rnScTgy)s7X<7^)37S_so!5t?ZeLr_3z%AaUQdtKm-+bASnQvf&Z zPaOH~U&eR7^at_ir~iRQ(z(G^z2hxA(ku{=el=h5wyqq~hp1tlD>{|B!)6=77cZ&O zvr)F)X1MfeZtS;1>VvrziN8nqRaQlDv=r;mk=PO4*wx4LZnHHmGhN}|wB5U9X2r#% zk!)2hcv8k-vvKZQm*cuZs>$OA;_>I6i3c8fG@gCmPm0HcrkkwADa{9;*ryN2 z42~(NR$!%I5*^o;_0gZD^ZJasMllrx>Dw(VtT9<*C-u6R0N zAx45gKbV-Qj^OErC0U^dMMfxk+9RTozlu+5Q!gdv+~~y(lBRjswsK7-m~y%JBGxx0 zqIzO|#Q*?607*naR7#%}b)07>1XR?Kx#cWVI5;${k!_8H>qd}OY>?adqYAcJqEWET zdkH5Wcrf-&OlW$F)(Kx;jv;+?XkPQjFX^kH*Kf|n?2e`cr!sP$YK;|WM&j8Y`)vHy|N2kj$fM6_O3Qn-w)wK_wVB$7KWk-Bfv}}P zm828e)gmX#-6(Xa1qew4KKdkn8q)BmaEA?+$*bi9q~@$vN7veJYPpHUn|l|0(&z;T z(RDY|R)lhV%RaQr;120Lr&6m%w>#i>i>xIbL_Z;U7h+0DR6v)(4|>{dq~AD{21Z-T zGG36@Rt+UnPZZ#Hz1>q_Zk4Rc4vJSbQA;R!N~B45t?4eokGKcanDReQh0^3W(9Agb z6GSU3=}yOM=Yd%0oPUd5)1ef)+Obx$THa*|YIk#4pB|O2i`NhH97L2Y9Yf{oV=^^) zbD05F^sIa#t#rUlrhVGchg`^^@b$AWu`iQQMe1+LsHxR5^OjG)4TXgTCw^trl%!H( z(W9$7a_QN0=oLRT=Yr(bIz+?;6k0xfLg2$6L5-(MAdWfY1PD@{52LUWg<=nl2EtQ$ zMc+J1-qc9qz=j_CuP$m0+9d_wOR=JlMXV@)Q}ex9f^bD6jEjQZynI=orvH|LaRum_ zqq{jUp%3w#iU;mL5eFZ9GLC-ev+;@l@`!>dJ)&P7P!Rqe&Eef6`6C)dpN&@)xDQMn z)(2h)Cq>VQAc2MS;4n6^N27`8J?tg#a(o%vWdyonmh5O=GF70R?L|Q9$X);seEm~r z)T{CYKLm`7K{Bkv1;DP*kxl5u??6oqKtJk3pbA!*^8#38DMw5GTrZ%5)Cj0F1S1*5 zZn>u80B<_VNOEqm7Q&Pw0mRdF?oyPprpn1WG0f)#$tQJeF^nu?2Vy1Qmb&Ii`*E5b zNf9S^l`C(Km7z0&V?e=6+)BZb)|f#Tj7hsqt9@c7r4>5^!(<8${u@$2Oi;YBvZN&s zlh!o$1ssbE;f@Z2I}qouT#Z+5-c(!F3&!K4p%xvZ`?XH}f%{_LnJ41J#8iCnxK`}Z zGMV$Y7GnCyu^5{gkL63R#y5ZecVk0Od6qPEKC2ORJ|u-+v?%mt^_A6C-JlkG1199aNOq@n^ANuIeL#&1iQ2hm3p&{w5=0V$1C=4@Cq_m~b^&gN`c~OQ;4(w7vQYhDp zXJGD3qVnRtPDvjPObh>Bq<2To)q<+FwdU}?4ND(YaGKPcdk1FTQ(#`@WtC=*22CA! zPHIOzlC>h!l1jaM-1WenJ>Wi0SBLDA)so!#WdM?GiFEBhvNgKXq#ZWBPic%U&Qq$} zaU~@6Fub6XCg|yI$&)ZAJ6BMBkpZm_7f|iM^FmZPrGUHukS+qz*W!v4^?JT2`tn$4 zjGJjfE;{u>(k7&bb+@mCKrkR4d2T|hauX@3tfvShRJJI@@yKfW6+4aOfI@)}Jk3Z@ zcjDr$pa6MHq2Pr|^GAk$_O0-_Xr<4%SnI+JTaPp-p$yCI!Pfz{=XHytMe8UM#?-Kv zkz_B^r$Qd}GasCQY(&2?ee8zX7?Lakaf0p~CD$x$ed#ck^}!Xb?|n_vN-n;6PA^YR zYOroybG)x9Cchc;b2s9qMiy5zH}}@9>lzK8i;YDE&sxdk&Do_meC%jEcwq%LO4e|s9YIatl{~jo}Qu?AFZ=+m;}O0 zso*f>0NqiQ0XtqyDHD`3akJWa<@q=9#E_ zU1fQ=!$y6&o^b~@s(qwArG8}Pg1&0{;8P!uZ~f`Nj!WPBS{!)lV{uyn`?#O6-~(EM z{^TDv&hPZDv#x*YjYRcebI`u1)W zr!ua5svhvJo3A}dP(gP-c8zP>%-@PpH=Bu@#aj>bWYg zJ-;7FD0Wr7`_ykOl;(Y9_iD>M1?JYp9bT)3JMSKiv#c@BHhiD0XvOcQeh59l8mzU0 ztS!e)Ogk>hc~;aT4xp5-mCmA40MBhJq?Y87i$6G)!9EiOsb+k+H4I!X5p?HVGPazh zQXXjO?v?6pMVn%WPA98gJ9P&!LdmD(N`Ob81xF#4efY65OzcV1T86p-L=j8!Liq^{{7ns+-PCr`ysKlx1DcjBSgtMWHr`kUB) z`b>-}09se1xT0W5-%8O>VaJNrUskZsmogbO_v$Rt5hIzNFsAvx*o|$aD%@J_1nvAM zejIDVH%vi{*FqN^bwgn8X#gQ1Spd%EtMSs0gGH5fjXKu{kZ66pblr*J#k9ydNi(`yBHI?dDQCou_~FGC#5Gio3W^uglRh)I){evll-ux{D`Kn zjOj(t39SOOq#M||B~3cl$xExUwhGAQTIoM5|7%qu6yfAlMY6;ruR_y?vQ6gsyRI@! zD%vZR_WFj|w&c?rtSXktHtDkGV0`FPzZhTm!+##1J@t5uPu{1SS(Z?QTj}sblbkui z7v0by^9H`vj=B;{xOx+5QYR#DGHPL#*&=mRV1S#pwK$|)g+hRLcjKnl0~-Zsox2E1 zcV(vc5&>ECrSy^21AU}D**qt(4MjRd-E;@slQa&(659B*oG*rgLwUh1sFg%zg2+JN zwaj$yrX4mk%B!ZSFl|{cO@Um{roSKN-BVzWDydE?+f~S&d=FTdMfYxF>(cC|cRjGZ z2eKQYIl0Yovakt|j|?;0G_aC&peTw;SA&Gas~73QOIWqJ(mu0|7Rp*6zQ9{;DBuSq z`Q-9@oTkXlqkB^F?xm9%dz&FLI$B13Ok*}&2HBDW+;h}Wg=D~|7+TY(97yZP1s$TN zdn6&)+Jqr^Guk!4G&o7s#hsu9J?cD8aA1xVRZkGOINA;!rLy#9L=nm8nE1#r9T#lm z>1V}tAjK%zxC~@ID;5rG#E&(ab9zcHC&--cP0#IK)#LNkSkm0sH3b%nxA`>uLR@|0 zY+SqaW*pqVKW@)2xqM!u+spbqJtLu;3Obm!GODR6_=EuBnm#MfswLb8VCU$8W11sA z5r_64jQxl7sQM@P*SVRhqyZ`8lFVB8x5BSyEkuX@(WK+ zT>tVj%=}0DsBDz>AGpG(TkL($E1(oClUky$TLT z^snoRgNet7^knAJ)$8$`p5A=;=YB;iBaX#|H!j4jW!=1v(k!)dkiMeH%M$29-5`gJ zJagH(LGt?M5;DD4fw@N42Nk@}Xgbxx@=cv93dX5l5$G0HFPEy7YXz7UJ^kT^0i)0r zRUNeGJq-Lu07)L*b8Ax#L?CQW$4ED~S?D4bw2I7$`yYvipZ;Kc@6Z1@p8m`~h^s=S zJJWypNqJYSZv_NZ9YRbBzHfIEjj2Rasa(4P5=3v=3LXO#JBhK5g|@2;Y5PTNIVOrS z`S13IFYC4)f1j^HXxel|-tH(xY+cbg4Xwo6*w)XZ`liCS8{cB^nt#W&z$h7S$!`)I~4s~>6)sNJ~B^s(1cuea<* z*S0HCbY2dE=rCL)4x)DxG>dw%t^Bg{Ah%s-Sg|+~fF*TNNap~TAa|&o4_sh$Y&24$ zUNCZQL2@I&1qWWxa;uQNSK$kql8iKR@pK1Ikrh-z8yBkN0tclSp|ZiUo8Vaj>WFQt z5g`EB8oklTE?bV61L2~IJ-V{H=XV&Zv@X7qrGaaW#u0F8Znj3rJn|(Nt12*>P0(N- z_(U{JA@EolQ#Tk>)2Q}MtyHq8AbKxBuks5Ew_|Nl!S(!nEGY0^ynQpSU%4EM`rQ1g z0`di|LUK!^+M{}rZA8JuZSZp`e{=(nbfXwLYOM$EBq zNe&)R&@#SnOpo-Z;*s~hUv$F?VAkTP=ROo8lbV7uuFuko--brx*EOM+wXT*lJUAzt z86CVZdn;xX?DGnh&c$#&%~r0OtBvg8HJqajo4;+fcsQ z@SA)|{nQetOM7x%n}9mPxA4)CI_GDmEk~EbNo6joT#XLC07Y6ong;z{hm_|AJnL5# zuCf*?AHhcssN&JnC>5=Po#%orDQy`$goys#O?htoXw#lvBVT8b3|=4O;%|h4V+>9S z^8}WD_o%&SnZFHEo=Z+h_pC1?TMxmh3*h8+r2dkv$_z8O@JUd+PaW;YqHjJWNqcBx z8Qd28Id=u~mo^m;VpW0Ns= z^}N=^zpa~I`4}bikIZbbu5~O(Jvkx7ADfCNqy{;aV@xSQC!r}<%A_?uUXXo{f8dkx zd;jPE827#WALGCyABvm0(cs2~bBJ)9wx4tY)D1Ss!PZflS(UhATDR8z}LqX35J{%jCcha>|7M&X%OtXVO zD$UzHVly^KC5dUatmDikxKr3*{h%kZj?l7HS5dg%7V`bDg8B{)_Y|0;2MxJLp|19g zo~>~`HTMn_-X;0b^T39#hD^y=*Ri(~RJ&3Tl;;CoH5)K>6Y9Kaeu^DU)3iV?^n!aN z&c|$|=4%!%r9?k%-Hx&_(PhzmDF%y+n*Mz$9n5FF2#_58L9ZWpT-2f96mbVeqHF|m z;H_x|25{)qu)*{N$9%-Y^uBZ@PxLX+L2;!ma>avHJ(89SB9F%NLd_HeMs~S=GJS*~ zlOxLJNRjZRqA!55YyQ@)Sl7#6(<2IE<@a2AZB zmW=6<`jFOM9@gV^RvlrLiAxvGhbDr=f^@m1-;e_S*&EmOcwX`p%M2>;9-Gw2_u+kU zQ1ia`%p8r8eS2eCD^=XQd_9Jzr{d^CkHihlwY{ME-{VJ4>t(l11>vegjWFU{MhW?3 z4rA+^3f6}etnZoHt5I>yS=P_%lKV!31|vVqVN5AtL^A`wtl#FsB%k7cJ63pZv%abq zSgBtQa((mTW~kZVuM*!t(KbZt3qLGOK@%XukFX|FR3{-RT3Tem1?nOYOs9hw$^!pr z&&B|TJmpdY(&&W@($Wdtd=AhO!OUN#m4m*5RR$c9fGkGGc55NXBOs@JIxsgA_(}E_ zVgR|}HtVIV?JmBCb54EJjV?hYH?H7lm%%qVbQLcFT}D#2k!EX;vO#}2B?C?K1~;+l zKKVHZA0?8vj^b}I6sk=B#iI%51!e<<#LcD7SZ!60^yiw|{C%e%jY+-SxugJiK!?A; zofUYN+y`*8D0J;Smd(wDY{ie>Q3%WuvwrOZJq_9%8Pz$wp!j@PPn+~4i|J?F7|6ao z`wqm{|KbaAR8zW+J^Gx_8$VG{srxoNNBor6-6aYrMV>A9$jOwBRx)h@3pgxjhn#TkN_vW)G2m zehac~i}AYo6;P=afT|PhD9#F@t3(%?_&(aaP6D$VTi(GcPYHW0nD$5R^PEZ}U ze#~>^vNys-htV`6Wz!|;4cbz`2}lPDhMTufJaW1`Db2Kn0R?7bI$phcHKz9NQvf;P zFW7ms86wg1IJdbVDCtg=i;kHxS|y+YGhbrvQ|Kd;4+e{19WMdGUNrw z<@^*B$VRSJ9<^2=!Uc0c^RDOTu4{#c)tK2k9pf5lURJ=os^Fh_rM&dGs$h3n0rj#j z+>A7@vZnFUg3=nTURJEHaZ*9;%%cy+Y58eFI`HD<&*-JghqQzsFG~`rt?6SuYYO_; zq|2%T`uSToLZ3F)+TU-+oC5Fp8+s{IQ%WvhosF4;tTJ&lrVpKnnbQx)kmjTA(VTDs zc~&$T)dzJp6+rU^M8d#DUAQOYtLsQ zxgY3 zksM^J<{_4Tz-b4iRYIX3PbyS~j4Bjx$#`^8JaPa;On4QN1hBIiBp32 z7@W{CM?4WEc^T=O2E5b_WGM6JjOoZ@gXrAu;niAFX^d~XDvyp`S?IlKtp@UnOw|Q9 zo1!QjrE>Olk#%DhLY=5OeYDFXAym$=J zehROIwprQ(dbw_NK2jIsdLLmxH>N|}oGO@hJ<0}u01I3N)2nKwv}HEh;i?oGQJ=A{ zz?${#8J!)VGg6(Ven>zgwtPvG>K@R!IjXP8Zd|_|gQL`wVB(e2+6FXTY&lMzJ{I5j z^FNJ4_dO63GsmT?`VsY2@Wm|mQRbVp+|+E@BpSZp^CN8r@URcyrgDV!S8acvKXl7@CSZ_MN-34wB}EXEc~6yov@uqtvdxJX5&#{#lm}5cWk`B!ODg2ZP|n+BG)x3xuxtgF{W{wJ`Io zpWxDA&`DZqe;%M=+ZCAN-La#QvgtqypV!Tn3CI)U@8@<;fw?P8N|mN*y=oO;KwF1` z(#j3|eyYFA>xbO~w5JIL>h)c1KJu8(0l4MieA1XoQy0p)oy{ zr41_V>S>1EW?4HW^M#duO}S4_+OaN}y2$ZWyz5%G`q02a9M_1|hDMnD$#VyFA~d}^ zcP_9>v$6wgD|4aB(Xu?&kD-jHXI;^>jhh-(o>$H`Zadkm2Nn*6S9Q-sLlZPi`V$WC{I54B2SD)(EBW~t;PwY9aAp5a6bLenPDhOs? z>ZGQEjE{}S7^B;K@lq39M-&t=;Tf;d*3qUCFMi^4^vkDNv|s6@--;d)-T%M|op)FeVYxOcFT04f?tHS2;0Kai}JZhy4<>C=ucm^WvgPg6ktnBfDGH9 zHn)xS4fPg{F-ClVx7-@^cSpjCQ;)^+rY_T>?Cmfm`|yTYuno1b;U0Jf}- zOk)xi>oV<1suu1ZcRjG92UuZY_JuFSO^y0`+J#yc9dgc@rNbzlhB)}eGnM<}9O^`P z`R{u#d|ON2-Ht~de@xR6_BeQRyQLjAZ4sIbewk9@HI_Am>1aRtkoZ>?z?;|jtxJQvqiHHtWMELNtE#E4#U+;{SsxL-J9BjcW1O+d}k zej^&C9}~@xM$PzbDu5lnalnAUvpF+PwYz3}O#*8b+ALZ30JK;ruC=VN(vDmEl@Uh9p|G2*QcuGNb( z9i=xeEX1(NkFW@|u3W3S85pI~3EbV067X=NmbrQnfsn44`SE!vBFNDV(xB#;GYv?k zepyvG4nQT!?*N#O_*Wg2B6!b053%CuGIRt_nCJ_r{~JY^tJvgScuaLvFy_7RMY zL0*{B!}ckORQ$%%j^v$w;l7Mml@BHXA$Y-m+&K9ul%_6`bi5%9%QFC=@Kx8mOMzzA zj{2a#K@~PTEp5g{+`5pfzC=IQrOF7ZxdE^fWmomTimFR>B)}BTpl%pxqk}rfb#qRC z!pEZU1@x(b1_z{@U&a=gKFQ{3&l3&@$HK>Ee7}lPnm0~>O?%_X+K@&%(Aj^+l?zTLob#hU0PU-Gf#gwzWn_6;?3`VEv62ij79ZlqdJd<)gj?6Zg+Wd==zpy z1~GbWim;`~MV8M&+m-Iwzz+?E_^A&ao7^Ao|JY~aKYacVM%&W?*R1Hd@DRpIfdAZ7mWziAe zcQq*Bh9NYNQ+<}EM5Te9x(D;Mh=!`0Fm<+laj5lOF+4yZ-{+QGn%a@@>cgsUQ6$kt zhAwOk9@(OdZ`52WJUP@iuqFSlITU*^shHKHI<;Xq74&`F?kO;Lm8d1{s=s{IBx3G*W*VgxYk9=czSt} z57Z2a7x?fP6pul{Sk+`efil4~M?d<8P8}|vac3F9Q>V{p%7+NJpQSMk>4!LfP@QW{Y-aG`$%pn zNBCTM`IU}bDWn^gM_M7apuaRUzAPiHKkA1%bKVzD`A$)uV_Vqphwosljey*I1uv46 z!c&4~0kM|@b};NBA1i+9A(p~}I~zxc3xfisBm+))SVazQU|#7=oOG*g-v(ta0!wzG4r1H3iqP)7s_JDM$!LX(z(OKRQ3wX=nPYcBrVNWgi)w zsDHv&M?Wfshp{NaE^t6ny;#q}5gYkh<66$$v@?BKCII)EF0}+gLU7V#rX!aRN@#K8s{AQWQBM&_l`@j2*_?xeNAs+t7FT`yjQqJanfn&)h(0f?Q z2ACHC23w0Hkd2kB`a0WH()aEQ9}&=xviYV%c2wahMamUr5{ot^?k)t9zOh>7(!T?- zVNvw!!ju*>AaqGo($2h)hV{Z$BHL`q3l11Qsi)F~5p}s`OGe2@Y2Sc>R8h+je%-Ch zzYG7tPxo0;>a@0#M*p6M*FfKK;+_I?OLa?YX|rp@{|-|tq$}d(1ZaOp!_-9GcR@Fbb9X4Ri?h1Q8~-i<%kQ|;a6Yt#pc9A59y)` zpA6H?Qa!Nobz#OAOc!ySptLW9s4PB&h?}x=l4%>4H#IN&N-SR=jit+LF|L;%H#Jp; zdE={^%EHPeE1E|>uMzHfmLe2vUUSFSG`(eB>E$K)YiKOixpC04gloEa7~Z4lC5Mm2 zkwa(Veg)j4Q~Naf&P$iNIT@YMPowbiD|Gn5KWx#>#M0(Vu{wVtR)(fDWk(}^tEx{q zg5tcG(_?ft!DW!^7XN!B-M%HK`V+bX63D77ljD6Zj%2tfI*~X=&3x@(P3n3vaqa+$ zua|oyK!K0$fW|j8pp}h0W!%hi3V@TiB`rMbE*wTw-9$|3ooqh~(6xlylf4>B39d zf{IwhE;BtDZT0+e8YY6CdF69~#zED))V!0;BruZnA;f z(`EImGsjQH(;xa|eC1DmKYr#{AJa>x>zelBGp?aN#kHq7}S~NlDm4 zRC%E;8(#{a0_Tk@)1-rCA)E?l%bpMkdju{UNWMd*2W8*+S z>P^hs6G)PwJ(?n{n!qhx{ePRgHW)#L_4#7ov3@T)S1xp?q0At@dq zGb%_@!;N(xUFRa88sf&)DFJxx6VBB^#y;TdtK8wmY655 z&%|%)Ym!TPF_Ps48QI;?$3brB1}LdarWhFaqO`NV`O4H#`VQN ztu+#pT3&F^L9Jf0UoUF%vgVPKaquBchne0N!}tt30CfXWV3H<-rd$VBAlDp0?tL{>Jb#?WsZgorDl3Fb_NC@pnVv&&s z1c=R;iSSIAu_Nqogd^++zxa&@N7xhL2Rp*yhjIA9ju?XtF?bMogaiW;gV2J700{)z zw^r|4t+#I7Dt|uTU*4wYXY-rQon5sqW_t z&ch3#!19u#LVZXOls}??;$JwcUbZQOVR)J6-|$1E*TC2vfntU@Fz`=YlAP<5wc1Gm zS@0?qLs+=hHDk63JxsRAN(I32mofR&uPd}0#>FQ;wLA%6WMm0mgvwVN#OMC%#`rEy z`1Yz25n&FF71Upz6cEl1%*$RSX3S5HHs?VL{7o7-R{)*kj(s)jzMp>ZTkVlAe!AW9 zy+72RV5!|j?(qY)_AL602Q(zfsDdK>Q)M9KgDQ&*(3v)!B*0-_+a^a+QaP;@IU(7X=UDqEs209n*VVG~G3-DChJ83)0Gn zH4{#FcUuAs{!W|)da^1%HOVM6lIe@okp=p;sFkWPqiTK~GYt>>mn@wVB{*KK6D3Q~ zC=YX9Na(7;L+s)+&A6A?ut{C-Vs_)}uSd_3y&K7Q2TY}R(cSvH3@OL?wq9m^NAas3 znCIr%DO#O1Xi*YYj=Bj1FEV!0?xpuUu<(HO>zSv|^0JQ&4-K}RS_XTCL_bD`C+d}T zEH{jG?NU3pzS6$(;5XW%Y>jur?RT;-E=x2Vv_S7Yny*OSdbHhel;th-Ru?_av0s3@8!*G1fs?Y-&j$eV)ywVA zF7l%1C1$Rj-P-WyVP4?eWEYjKZ+@w5@+tVg`Gc>wuYBo??a^<2yB%j|lYPwUpL&w7 zS3dJh_WC}}jwPq~;@>4cRlUyAnf(m553qdXAT!rTxt`!S##bpQ+zG{5?frZzex2nuzS!y7Q(qFx)_3}b1JWU*J}tGk5nA2M5xMk*o7V*ly>^7YSF8cZmnBEpRX3k37WCvZr0s|ASqw3<|ia z*9ewLmU*CL1}YNRxbR=p$d9~1(XSTj+z5eOgMsvQ+l7WeAB1VwD4kE@3NqKLbw$33 zRy;On4|$Q-al?;O2GkPB0_(V{_kfy`94&&!p^!Jn7NR0hfAMs~ohKKg_Hk zSN{#w$LR?Wdl)_rk5oNM9XG;>1Coz};Z5H$QSTfz%j~ZtB3W;(>Z#ujw0Kxm zUk~BhSUbCb&B%GvWoMyDZR*1MQV>{K592U;Urk``OCyz>RmZ+YwtAY74C5kJ)9#Ut5qDS;mqk z(gS1(suH|$0ibs_XftlV@2%~NfAfX*_0N5*z5Mm>Xf(M5?VSC;jjy^WQ>AtU!1`B{HAzQ{~NEOu+z6{r6Hh`bAXb;Vd`BqOQvmX53Vr_d&h}((+#Wbwv#NQaA3@z1D-XO#Q3rz zYg$+neuTknW{FxiyI~1@isx%yo!;=ks^>NBaUz-b#uUNVbp${p^!_gUBlE_koV9G2E zWx6e7mWC{I9cVhJuoFm$!_%&CY8c5^zjP9z7&%;f`;?BO%hHb0cU+ZX;3}_9BV=$g zHX36)JB%oNF(mJZc_A^H0B}I=0fH>8rJ^oF;IdFI8TxJ@`4XcEb#>S|2QjtC>j(vN z0jgy^*5VFk12bh6)BynlIkRSPB`YrII*q(h%E{Bll&$#G1!rneMc?Isw&=9MjJkLp zgBEiHYoA^Uy>t|~JW@vK0QFo2YtvwrB^;Ioxxz9#7^Z_`=gWQUJ!%b6r_z6L>5PjF ziIO$SW5^Xt+8Xc{P1TcV37}gb0d>*5L~XNt+9gCpfJiYPA(<=)SIBtycPF8Zda|lt zOSG@63fJt^6o7mTJuh_=BTJ`|+2wS8ewCJQXK_EPoBDD$p}y}d_l;-6oP#h(4rU{*&t5b40QI z82aazBw)esHeKT0+0S0Jo9oBg5AdbaPyEjR)o!};o_65oyLlhR(Rm)X!m;d5l#|?L z;xXcLtZEkUE+@WAJpyNd(-xYRe)k}`HqH0ijHf=ucM`L7%Z&JJG3e{T9R&R}=}}6D zKOCc7vMr;^+sRV$5sD4v-hmU_j!>v&bHzQIUN9NVxOz8QlhnjJL99$LK$4eRqi1-5 z@o~25^QB6bR&27y(Vb1Q48@;TjWbZT)5r`s18R3g$;W5-g5((n!e4ysud~+N9Z=j( z(2r<5`p~x-{GMrd-F0_9!?UNEou(3>XOG`~PuQWa zw&Sm5kPE*v%y=K?qd1%A*eT@63+?3HtiOK6y)6GY!7>z=g7Cqc8xFOT_pl^{b=d2y z!}g`mti@*VyvE@BGBeVL!F`w|CpK)ay6^t>wXc7@ZJ_5pufD(S!|r@wiO$CxNF19% z$D}S9@g*)N+9`Zx6M6cHC)vh7K7_i7QWnJQbE%MhB_FBex?2%d#0X`pP;5 z+5{6RL@Be#5JM3BD?M<^4!Nj%QK@wg0ECShZuQH<2(Y}{l6qwQHc(oTK_0fcW7I6m z{74})Y9ufAnnm7->w?Uf!^;_XANu^B!PBRCIh*$E(#C4-^^a^_R1ZfiVPUdilQxev zZUEV`k-3Z4yy5NbcYou5ZD0NLN7`%O_I`FsqQ%j;_`%w>Jvc^4IVWE<%PIu$)Gy>y zPc?HH9JmYJ;@7%kH{H=*|F$1)fANtIwYUGwKW6s6mj5jA=XmEGfZP>R+;guJt$RqA zKAQ8#y(Jv1aPXwQ@(8a9Dn{8hW6qhw_u&OX)!jM&d1LWW$0BY!EGQOfPk37X1pJps z65BaPHwlv`oe>W>4H+cI{WX@Z$R)J>Z2HPT*-I<05)hQ66mgw}__piZtIV!G$XBSZ ztm@yfwO2JTAH|eP-nFjI!{Xas3RM}po>jWvvC?~lf4@BN+28xW+ed!=58BDs+|Et0 z($4aE^z#q#qT@@i96}-w4mDPjmd|i@l z1X+&p)xY^lyZO#L+D*6Jmd@=o1NjHP`e3{1_LsDicih2@vr9vKH3p54XZx21%Fda1 zIy@lGNI6(K#Nd4YGyFu%g996Rv()6t_Vgn>B&^-gPV*9Gy?$>;>^xz03iZW1sm;zA zUNp|mE-`a1mTSHbuv5x@Y;Y-0wrc z_|*jAVTlNK5H+0g9IucD7`YBihMdybnfs}a62J3Nm!Xe?IkynZPqWAnB~}L4L9bRu zqtM4P>Ojc{DZDn3qb8=~fwDb3)WN)X7dZi@%=S@U@iB@C4LyD>-m+3JY*hyNF{a0@ zh3+1c!9;KEv1@{q0k(3l5)2GnJtB6|!R$~6Ni2CM2H-N{ww@Cvoet%ok(LPz}N zOl#E;?W~x0n0831?f&AUMXDoT`HgdS6e7dA=}!8!Hd{?ggSgf_9+p>GUME~$HPAF! zcrJs8{qQ=^|^a_)g-$~rSr zD_eY-6h0SOdwzl4O6)V0hPkFP>XwMISR3VKm+|$i~|H-wxUU zPiWG9^fsZZ0smyAt+0EHziix7JPZ^-RSB#wWnKgtU?IQzZ=1D5EGC*nV~ zQ5JTI3ZGu>LgnCbei*T4l>gPW1}>0j3o^B}w72va&@fP4CC_&T@=FKxS`WF`_z0#m zaFa(g>GdQT@+J}|YJ>6#vDGwzi{+}0S*}CeL2j}jEw3a=K0|poR7aMCYNOp`@L#Yb z^$Qu=V$C(jL3xBpV>AIP!E-!n(8AwJiaD%T8jXR((n7>rimYc|S`51rNGW3$W4+3H1$iCMv2jlTiX_vN0 zTK{SAaI7@?LO&5)lg_$x{iZF!iwadwvp2HF>AD=$M`P8Gkl!v(iZQY z@IHvUD_vl4?$(P|%&lk6^8JJjc<3!XzQvZY``Yn4U(sIw)_1l)`PaYMe&A>RNjr@m zzUW==hIpUWgL%BD|4}m!FsXyyJKC>KsEi-{KvM2a3`jeKxY&pOF12wogZ*UN8{YMv z_6NW5EA8iA#*&tUH}j=XYp1mhNluJZEa&M|z$vMlCP#H>ya_y6$%RO|Lhia0a^dI? z5VV0m>!$FK5DOH5>1F8blCd*yi6=L?qyA=uM0}o(=mr+#9Cy0a|5WthrMpzh%>W-pPhB7SuP zb9AkbrR=5wYEIgh$}tUCD$Z^sd$2tZ?C^jeyLs17{!+W=@l)-d2kxUSKG4o_vy8!1 zb(V|zMJ4lI@`3Rn%Pv+<@%eR@sO&p-J6rSlvZZ;<03V{mIq(<_`Bpp6r{tg7@ZEm+ zKx_xP3VpvP(TGkZSrF2|*nzq|*Lh~KH`vDUJWE(EGoW@xNm*MAtRpBlvovz>c94ll z8%*K}Hn+k?w_hDP+uo zqYtWFWYT%cW=NM#CAq5mcsLRpkkkqZJbCzc=2>*5SkQ9HgCe87425|h9kateB+P1e zzD0p*aK-O>ebPP76K*@euS>j}VQ>&#)JaY>;Z2^)>D+qNhqVNeJ5z12ZMfvqB>yhA z(?50R7P=YE_QnEutb2fSkoN8jv|Ef$GjIh@wt2L5tvb`z4tZrQJaX73Y4~gMGkMlw z{c8o0OLV|j`!D&`Vn(#goZoWu&Fv8;I6y&99}eM-e0AB6e~+<))6M)s=|ww<*C z?c=A?F#|kcJ_zn}=G})X`SFg<$=kQ8zo!5IKmbWZK~!JfUi+4Jwm zr1}u$){Q<6*p64dlV007UPPGg7j2cEP`_}Rux<;NDM!!uGSz>5j;+Fq7m?ip2qE98 zUka=U6jPi?Q$TYcvJ1ln-S*t_ZR*ALjHF6WqR?A9hUgvTDz&t8AZc&$RLS|_Pl`c0 zuEMUTpk4ZX>C(BFh-vZ+xF)6z-+#xHri8XFSIAmTxd_7-e}KooXT;Y?FCs2&R*N+IX2F+h2U8R&a6k= z;w>ik!h4D*uHS=V( z7B(M8+1I<)irT5j$vue=jzuszJT-RAVB*EBTdKz?C%!#W!?5FGPo(w05>74JJKa3Ki@5tdbt8 z2n7|W)PRoi!1+B5P7IoHld;e_Ii4tt%s%eWzh=VWH#yugSI-eD21bA>SL;M{3KV>K zBP1=c!sIbH%;hR}!NCDv%(7E_)8NJ)F=bZ#t&0^aYzNR8L}yS`1KH@Ticzy{NWCc` z@ODrry#OLT7>nup72?viOIOVaRozWb04VLqi(cNT@Vy=@B}a&?!$I%#=tpuHhBvy! z5YP69lm3nNU_x9RydNEOqi+}vsdCl_rE`28Mad6sya7r{^}hAjIbW_W%JN^?Bii7F zy%~_hMuEa(i8>XI;7hbW8KA@0ZwfrlN4(DQYuPDwZQ9@kbWQVp5??}B?alM&YS$!v zMHubUkQ*SU)Q@f#)WoQFOj&QPFu+~8z#if9<14xNWX_8I`f~Z_#8r2&6=e3gJzkw zUYa-Rr`nB!=nD>^-vzrYSY#mY+4u+EGjlgg08_=O+Ktlp#rb`q!{2ID`>UDAJ);qfzGhIFQO1GZrUXkAR&&#!c{7qq^+`;%t@ZK z+9jSxx<{@f0FxJCpbYxC=!$pABThy=a72}fE;^}E7;p_!Wowi6nAa6I9)}EjONk+y z?BH8B!>4<%W-HAjR7Q`ZlV#j(Np?4=Sb4TY3dK~_S(PAF$`TuR12gJ#Co%<)p_+PC1+htTW`D%xCeYf zA&p1cMZQ-0_ILk8`}ptwZrgb5>-oyEM*5uuADpZS4rlw27Fq=seH7@jce_Nj;OjSP z1&6Tss~k+K?tn?xXJyp;CS+GH#ueT!1zaBLRDUp%DgWIa#TlDo4RrBc6jCQCUqpbQ zlTNf{SHX*U7mfm!Np_i{Lgd|y2rG$$&yZfwk+SSwq;7-sZ}~b}41GJwcU*@>lkHZi zYZR`ARoX7U1bCiZD=uvKNs+3&J9W#=X4zQALE4G2m)`Th?jEq(*zDz{L3;>00}j{P z`7%)rngNeSa?v`8vqC54t6eTnS#di+x`}iS;QMp1FF$2%Cmn|A%s|^yTUll6F;&_D z=L$@b>12rHW(aN#JkoLW23S ze8pMp91Q9lyt7NnMYmm5Sjv%-bXg4$O&+jO!;}}%WNEik8kd=*a6yk>MZWFmq4oo> zy0`se5j42H`vPc$SrrZH@@{9 zSxU3Mbtd}QHyFypiAuqruyGIz@5;aOYO8oQ8R^`_G1l-`I0 zDTnqfbQ*HTwR|&D+plVs_j|0g1kb8!)m|P0Zl3XNA;4f1@yHaO;C0E(`TKE@=l|fr z)%Zw{_~rfpHnB_O`Z-mNDfh&-h3dmgar^>)wNX9(od;^odG=?1hVA9fvLl5dvkO*~ zsbG60C2v&NAnu*^Jn#ZNV1w`MjN|2W!VJ(e+vZ(i4W@-NJ$^p4$;_Hd^3yc;x>(IL z^^k@_vQ_G-O-u&);MhQpMzUJ4YI>zh-OW{@P(hnNul#OWuV~SQF_Sa8+W8iMgRQWk z-x_mXfjtL=B(vk|9d;kOVuo*RAqg>OdVzeF`Yt3j+M{EBSohi`*OW^AC- z1aWDO@nJW@#3poR#OAzY%qMU|D=HBOwK+&S$T3el%Lk^YjntdiPO|Do$y?SF^M`-p zw<(PuGVNOeCmLn@hFu4wULh??d4Yauk9f6&pbw_RA!HCOkFe)T@SMBIftjOR+kEn? z_Ir1c6o26_RaEaR@sgkNK()`Pauk0Knd^)oeWq@fP=#E%r|O{vCeeZQ%m}Ha8uA3J zUWB@{jY-+>N81gRlqT(m*RC;bgfqJGFgP$xJWS=skvvXBY;%H@GLgc}f>}>ZhE!ba zHJ$N_Xkw}cKzA{5Hq{+o+{gUZTW=%9z?pU9u5ES=c@6$~=Y-`fFS+A3)`g!ep73#| z-9x)ZT^~)467`m!ePz^FJnJA6-o{u~S+c-Q;dSHAM~_Bcyg4r0eX;I1>j$hL-Xif&b& z3%?eJoRu{m2FiKef9S10F7q+AtRnXXcOd8R&AqRC8#~54*uMGMKWc|rTIA!2)BQ-R zzC#~-nC?e>w2%vU*C3wdS3L>_Cv+bJU-A*n>R9ECz|)OCdL zDWLs7v%@ign+Z5uY)=1gHnbdj7;u6<--mG)joVU+t&yY4HCOwV;F(dpfiYh;^c z`ke<9I&=DT`})_u##*??!)J6H&&u=#RJ3QM-OKoU=>Z#kI|rLKkIe2+k9+}hY!~R& zuTFY(aGaS0aMEZ|Ppx)|()@c5*?ge6i)_ zF*Kp6^^$0!GCA*$aHa2cC+d6Vm``O83w!%Mp~=yNC2>A4Lih&SM!UG32^f7 zAS^732k_!5y#of-@@&AC#7V0oaWyd%xa4awm75cJ`XlkWGc;3N4<$$)zi0z_mG)dE z>p6ZAU$AMh994Abqda`qfw51rLu0v*BPUn95517h%91uu#%m;dNEL4{7kTP%lp$bJW^+*XR;}G zt0EblE6z^7R>i%r=YbdMfojaarV+EDU8MqfJ5)bJ_if`wO?I2DY*&|A66&9X^h{av zOM~TiwFhO|oVxOGgGVXBgjzw5Nynb=$zKb)$ zVmj$t2LR$~gTFxd%9sxg)q$D^y0X*-9%HaMF)<=ef;eb^LYYAXrfPQNW8JN&qa*Sv zvF9>@GlReKkD4ge2e7UWh+~-oBm`)~Lv0OG^dLZAGuT6YO3YtRO+QdG%XLDp#&1S)=dDM)K?!?C>nKHrXv|&7k?G;-l?=Q5<+8r)h_wg=9OHc zf7zcb0MNoCIygW>rxo_xcdI`0GI#Up_vLdrv7}eaP>+luo7~VpOz$ zKZ7*yp>9|FnR-`)S~)m)vZS+I0>No3a+Nzg%wqtXIPg(tE3Gb;Th)tEhXpz_?2P@q zi~?!ujKJw321NJS_I@dwp+moH1E@_X?&rZkXcw6k-#GJFJHf|*Z#~Y}85s21PNa@` zav6$Dpm8$7cWbhrI>q94ft%oOk>^)@FMzg1723zl_3_oscH;7xcKFF}w#%RUc>DGT zf2}?9haYao8RWZ!<~ZMQIKnp{EF#x*??+aadNKR$9_6u-^JSL#XfeXu3QK6x;4nyc zFZA1Ac7MC`?t9u7KK*;`24>^=-ZE}rZ8~~HUvzT0xHRgB1QLYEbXHs%65{s@9@Fs} zCyQSHENhqI;G?_lYq#F=vi8@X`2BXnst=R2qu`f|EKip%N-k4brxl@WIuch)fBFNh z9s*QK0suv7Vv15Cq&3C|NXu6I<)IETvyyO;jdm<>Gl5{87-VMmprnYEE)g;kFMxIY z*+P6+)Duc(*MVtmD2q+;EY^WPJJ*TTi-=rXZZAUcRiKvlbjGCp@O7?~dvB8#7*`>$ zNNe|^7s2Ki!2Pv>Ps4SJpSHgI10AD0dDG4e&AWOy?@9G&$K25y{OY4JqRuGLT1LUOQU&%77qRU64Zq zON$>8&f(Q_LlVTq0uBAMoC>QljSpLthL|2oMkR_OW~j(Agn^1W+t-I-CSe+Fm-y%} zbx63*oPfkOd@Qg=T#=G8AY0qx=Ro^u@D5YC%6n;p0C~%${=`T}MOJu63vHFUhZrj9 zg4d+1uqiPpa+OC0U8Xue`@;;s;VCF`YJ3?!lY}8(nUPP&-eKdH4E3p?zQ*G$xpp|3 zbvXmCc$orcqA^)EhxbIU_w2|!zs}2nr}bW49Yjb|E(dRN1@<{0loZ&cpK(A5oo{G6 z$cGMRb;HJdW#Y7=M`ADI3^YN?191kiCcuS8cdDO5A-(KiC^b<$;`(hf~*ZLQ|TpTv*z4fhcZ^uuZ zY#)2*k@kmQ{dPNkgb#(W9(;{^`@*Rw+hu0E*AE>JU$<=Bpv;dl`n}BH`_R5~?Y2u# zyEweP)zj_p=C|7bU_hV0x4-hm_Sok?*Us=B&Ki0jWqtV>zHrQvZFgae zZR#8)yM_B1h`Scua;_}Pi2BKSwxt#mUATOZuY$g-efU@Zr*`tzTiS{H-`UQyL)Jwe z%Cb}?h0HrY)QPGmc~pusEpvT7{2p;#X6ky6M+yB)`@}1;>@st(z3IpPVf+1m_0QYQ zU-)dh;g#RVmzmwy9Su=XdmLzYb3$GZABj$<$(9rz<=*9$)pDuW#HS}G1|xq4OtHF; zD%q>o%qD!)uUCjcwIf;J?Lr6N`5x&q;9d{E-f`HHzrmQRY?l0o?7*vh2lmqK1NMTh z@?Xk$K}6pjNcu}x3~!eyo)?h#Y^1MV?-xBVZ}Z6fvY+sM^2sOLH@@*rKKyc<^Eqjl zEWDJ#wbS$JE#2dA-FhIiTBG7qv5b1FjE2Zv0qpqG5%)Tm`qwRXj@(>6sXbE|AFF_Lw+4pwEaBrRchEHraaPt8zGn{?iIn_ zrcf4H0_vzn#WTTEW>C$ebFtGAl=GFRl3#I2}9}Z;Io#ql`|`IxXP#=G{o1#(UR#?THLo6AX-_`kreyBb8#24C`#~y75S&O~@D-713dAgm3$63DYd5XdJ>1R%{ zVFLr%_4RBiyPvIN{ra_S9qcZnkHFLy_-p#`lFre891h$o=r>|XDzW^T z2ghU#G|OhCo#Qjb`~2iXWqyk+|%IM@FjTH zj(7)gk){G%yhoH9v^i(w??$im!8TezCX4w_oU%LrA+O7s#FTEs8V4A2V=5AU?YIsc zbZg|MVej$5dMLE8Zps=wPKFEQ)bQw2mdKmlxErC1ovrM=z$82COXUMx9EACwU1EAhj29 z1+W~oI2@x$64OauE+YwpKo|Cz&izpJp*R<*ba}qPXEk^Ya<6~dSK27o*=n_OYur++ zWT>O{#6dPI971c3wpbGS*izCX;TG3m4G)L%&!Jc=EFn0(lGO}l>5!#p=1Vj1`qgy# zN;&Z6p}?(CJ9L&?tOVz^2w@l13HnLXO=Jni#3++>X`MI{AEdW=K9?th0))+MV zbfIT?8fiD4xS{>^CqKy|kw0;9ck_d6=joR>*vaMiku}z(Uv8(_5B)rOt||9}Wh;C+ zc@0^vUq8%r+WP8#2Cs+PStiUjXIdj~7APWnSWp8(daazwT8z10^03tfmcN1;Iu z-iXat{USw$$$x<4(4kz2hgIFD+w2$uQo*Ge0tdZZgpKsb7hZBCT~2z5F&>J8be)`* zmd$Z%oT1y##k`(M&efi5abHyUgI%E|~HKRsxt^VBFRq5nqP(t%iBalM3Zu*}($_>`* zYBhxNtmezcKkJAiCIrC^zB4+h$TXFTV_=lQogEhP^D`|Oqaobh=z+2ba8_hO=`yI` zB#)7qTYo{gOSEj@Jki*c4_kLO9;Isx(U`^Iz}uZ`9O!t?uq--?=f8grLQGU@sH1Gf zPb!RzagF182Qy&PRc(71@|I~~X|c*L@lV&Lved=UuOy;NAj!x{6*72o;DHTAqwKOz zq-bd266SpQmF%)l{K6TXeF&tc&Q-a0jUW)STn4O{?I_-$2YO8Zq)95g$bJsv^>M*n~^HcedK~ z)n>b|_ZBbX*)@;)lRAXFvJfqCL~a*nNn;Car(bi_V;tI912Mg#pX0L_k;(wi3aS+k z)KqL_EAO%nR5U?xD(S$Gw4n`qxj+x?QAQ^2+y zg5nu{^Gi$sqizH&6M|*PyAI4oYu~2+;c4fFqsQBghfeb0oEsTXvxJ3uB@5qK(c4?h zbh`{?#icC@Wc%WS47_i==|o#+u)VT=xZQBWP3>iOytLhY%gOetm)zW5a%8>Tec~AF z*4Nq%#19_a*Nz=O2v?VUP>)zEUOTr~7vQPkJy^Cbqw$vpgF5S*abKi<0TS%t#K`H@ z!|nUt^^@(^_nx{gn^CWbhuCWm3l)%MEjN^usKB7!Vqb9$ zI}ahJdAI1kH@>qy_|#L7uCKD0%-M_l?RdaJPCC2l1*Yn= zqYauH3hLO@KvEqun+0+@bn`TWQ`*M?4@ev%oN_hV4$Iy{j#nYaNH~XdrhoS>RNz)EUWG9qkVB zsC@YtFW*$h4^rR&HT73MEy!Ey9cI%PV_LAdk8HnF>+_mV;EmL6oSnHja z-Y@Yk3O-w*njBz8etqo#GwbW2E#=f1Utzav=>2MP>IC|y9(u)3^`HzAalgE&JAG-` zWlfil-Q3>%zMpF!{on`M*6D}ap*71>o2p#vZmb%jOqU;a&%{rjsd*A!5|N6iBFWZ0c^Z)+pfAo)kY4xA}cmGsgmoM=1+cZE^e9?q zj_i!WTYm5g)6QG|;@b$0LDs}eRPnckPA@EiIQ5&QFg@?2*~Vaz057!FRHo6CCr7l2 zZ22!ph}OU^o~W#}4qfIB+{szbr_#vJl~Wg6tYxZ>mce#iOhxZOAI+xUd-iQj2dE`v=5ym3*uMr@Cj-C53skdzm+ z{3mz?1Fngbg2tUh=(?JeAUfjv(3-}s&{Z~i^gIkWkD*&Iz>0hvZ65=t3zyF^Fh38r zjB7x9nUAIED`$!2yMmFc8C*l_PE^cldp}=fpl^Nior8UDTf(uAd}n+wu^wB`ZLto$ zepT@S4xafXDISVU`P3SFU@Ql5E&65rmT?He^DwfoS=)Tv(XT!Ptl^VW8|T~6JML+3 zVmrf+eDK%#qNw|$GdQ%lA>>yLXhu2fla!0hh#N-zGdap^^aof0hHX1*^U1d^Zt$Y% zt*>}ZyW=G2%R0exUSx@wSJQW6H9ZXU`6% zN=I>kOVR=NY8cm)YJpzZxg0rZzu-IR)nS(TFlk($O~%Xynr_0lN|XK#pN)Gse8jW9 zcSNOm-}*;RP_Cbo_o`l1?`6ShLBRsG?YkmhobN2W=r!iQ{L8O7?oYXUkv$JQy9ccLS$RmrM%&8ez#<(q zt+O|cI2G({!2lqhjki~qnv9#sybL`vh+w{st30Y>kju=?RDu;Yjf|-pEL7uW<0Gte zu;CHa*`mze5swZaxy_)RNMVzx$RtinnD9zRYi@LsA2c?8!!9z2&pL)vXID%;myUMf z6|guxI9$U^y~_endk}X@5I2o)qC5WXtm3=$iuxLglmXb1?qmT-mMIeQG7#e|s=l%e z-FrSh5l+M%Cvjg19SSC<=@WAH2tF~nJ8YbAlf7#MhU zmNzw(_*fPqtu!S`-q@iW4hNhT!gxW_n(VF~dHGSyD7XfUW?ljYLRDz;>B=Be-x@N< zjgNA}`K6oA`x2jDU!el)zwC*fWen)+0Mdb|a$`%)_HOaesvQoM5)h?_HKfYXO%_)& zXf&sM03UdGZ>P>dtUhkj>BDc@06553?nn>M8XQut*lpP`mkSS80%Nc8-6{`qPP(9E z)iMNTh{0Q7WLW!rtC2H^mbs{B0WALt_^vpv3!bU%CzoL@wkW4tMqVI5?%GX zHle7xTO??avL;1(+@;1HTsw!&JhSEC&gJ{qClS0g^3sMty@lTE8Hh7$Zn1*V_Z^I` zCav>1m~G$EN$7Cy{3c6V&Q~3=EVvg^9-+5%v{8l}@~}+RT^r&}WwAz^fqCP>KwISN zthcquQqeDF$TwR#N=aREP5b%tTkYOA{c!sxyTN?v)BmQu{I&0FPy3Jnt~0mU>s(Hf zLRWQ)@}WKnupU@4m%MlJhuUm`oIlc<>DMkcQl^&cB|dhv*1q>$?`gmGfBX;au6thD zPTckS_V`8o081ZcYHXr3L_lwBw6D1*fy4*F2-Xw}!JOI_Q&06WH&;b#R38{gfh7JO`w%da(Sp9X_Y5lLpV(k&0`IRp4`mx^oMqc$5y-6@9F~$W;Na z1EmOZ&izJyRjYxxK)4>Fs@;vNI%B$QvF>|6%%x`D)rG6#_3*V!#1)8z4+DOl`I?R$dhMeas{XO0$ha1l$fJ% zjMM22A>b~ep)4I2Oa=}a=8ew5r9jz*q!YyUZDggBvW7gQm%K74*xFq>m|OXw>2?OV zI8CJTrl2y*J3G%^v=&Q9?1jP{S?8ie`G+A_h+G95(YVjf_ z0no?~ZZQ%!u}aFi{9#&KVh?f1>tTvW6t!6#zC!~?5nbPqk_uz#lBd4%FvF~(RrjhJ z0g@_eMR)|AQ|et{l^-Tiidgg!m%I>|TVr;G=jt3is0?eU?b;~Q0*?2xlMfgK9t`X-0E6n*+RsZR3sff3iCUuMfd;~DP*mX{Q^3x566+OIeJ z_^l68Embk)7!kx7XfyE5Lr(I9^gQ{iSXj%A5`n)qWO4+3F7P1!=6~;J+GjrXsrKZ9 zUv7sEkjoR)J{}&`UK?aE$|O1$B>eP`d9hSkp{T2x#?PILwp?V*{pzuk?Z@8t58EI8 z*1u?{pZFR}T3p-C;2s}4GdbGh$V{OCB&ffz#B)*3GL&3(xblb$pAb(~dfciRu=LJC43Dgb=bF^Sv**bj9jhC!crXRR-7ZJD)dAIY+YM7{ zh_NlCjh*tk1pvL*GMHXs3w*u+t?b0i%xfPk*5IC;^zdYMykG*8rlfZrNN*2xzCNMw zz#($>z@EHH8r_C(mvlqpTsc((S@iR&dy9hEVf-gbX1=&uucJ5m zIml3;`Oe0YlRTj#3?I#Y)@P-jIPyr{CmwcOt`yzsfo0P+B9 z#aHQI<0lNyEWP9@6zy5?s%ZFImVTtj!L)1Cy?-+Z=gJ&4e4L$UnCR1ww)mg|y+5uv zL!bJjDjdVSOT5@y{v#hcZ)byktUqz(VqNBopzu;ZTeb^41+1UEv%T#Hf1>^AM?b(X z0cV)?_Ll;GYp7pH^zBqH;8NaGNG~^wwZ~IW#1BL`F>Fx(OoWg;&yFlFdD-jQO($<{ zfAz@^wxiOgu6D&kTk&meM{JO1g(`x0$rB#&it!Lr;1+%eYO0ZL2lf{hY>1XVNrjsG;kp8UH3e&iwB$?$zY}5 z8CKfmO_GK}7}ZU9t~SmcuLjRc3UKT#yx9Pc!${3|(siY$vmSehTLZbNG43~{vdQEy z6n-U%S8p`kyTJ_KqA*>9px&595&7745;&!UNU$1s7qnUf|~jfyOe1nfYt%a#n*LWT#NT$iD1p8|Jo_E=x(9=K6DFIY7M3%yK@sl!1BGgYj{)xLkd@PM7@hXYPyP7vJ>c3O&&S{xF{U z#kKN!F6Jpu-wGLrF4aC~>hEQjmcRJZ&#+6&7uu0EFkGi^MbKUP09j>2X9Bo5B@&#J zBiu{R4&Ut}`;`A^BfXCJOrWhJ6z|ek{S8rVhhK$jXN68b(p=+%8KIjHPLS+&9lUnC zcK*p4v{&LqcVv1s#P1z#(r4({;n&5b>s&Pt?=8z>;hJl0n*O^c!KTn~$%amaQ$R~~ zm+_$Po%THN!aYDuwaK;?)xFQHlC|Rt7GzqXgCm%F=na*|q7!<_tgrb5(?O*5&U(3lSc)y!!hOP>}&BHx!$9NQ8$a_$)J;CZ09%CLwJuc`1=!Y?ai(o z26&}183!NU-P}q8E8mj`llVqdLhvM+2rNrwj-&DhjrV7Y6M%8C;-k*Ua#X9V;icf! zc~xQ&2{Tu8CNXTP-13RKh<6dcoyV39B%Wq-h7HzrKfyldXErXjN1l43J$d@6_TVEA zwG9U68w`+{4{sMNf8;rvE`QaEZNM(t$8#hgSRIvP@H#Bls#Hhy=(&_giVt1Y zA$nZNyr=5F4WeKm<|#?1$D?~`p|Te-cE>bQ-1FR|x=c)*&I zpGIkP)7a5yX7Ed53?!UI$&D`pAq{vU6)#}Uocb+h18(~P#D;L(X0AycC}|+c*>i)##y|et)ZQ z=?t?Bkn~sGH7T29mQ?SnaSv<0yS%V13HS^g1?b7BH{v9UdG5)Y)hy7ky6n_9(wJA} zRq>8Hc#eAPwY#t$j*2!6P@s5}4sIt|c2%zWnd5}PG&m)$h5P&ofLM81p$7C(CkZ)E zq1%--ab<3?jK!Bezx~kT?bNBW?a{}caFKXhV^Dg>EjPA1ZoaX-_U^md1NX3F$^#Fy zyKlO+t*{<_b(40LqLj z{=7iCa`Wx&p8Ma}KJdT)FKyrXr`vvXnjWa6)lU9oWxP0sX;SJVe-HhT2Z7v|-lP5A zAZKo3i)Z|E6~POJCmp{NumVj`~itf8DO)1#%Zue4|J}l`c(k?y`%n^cXnt z4f&VwPv(z^57^)j=h)$Ow=mheB*4*WgEQDlVj|6C3Ls3a{*j!#U(xNG5SNkoPD#UP zV#53n$V%PwmUMw3$g;(Ubu~T?VY|TrUuU7pR9h;{vGR;{p==!(SATucyRtl+NN>2& zK#H@oE||8$>{i6j#&Zw8=YipY3}8uklcWKvR)hLPjp}u)M}uR7YuaWdjk~lmgKk_C z5l5Z{6(b>iCo#ArhhKOX7=uUH1wRdZ#}^P8{W_X9J1pVy95=|o>%AFpk%}Txv@_}P zo77Az8*zdXrlTYCx-*mk@gFpbS>i+wVI8>4i!WgpANf-^Nb<2mtAP!V2HxBW;Y6(R zXISM|XIjuY+mLI6HX8o$ zO5_ga14G;8ms|)*a0H9aGM#VoI!|#$iT9MLt+OT35&R4eG?KXWQN^lG;$f4?DcRBp zQ8)i|P7MYVRU8rXmFsY2_)Xbn0=I%2io7BLbf7fwaHdZ#J`6e_kW*}xE6mhE34^L~ zIa91k&>vk=r9)TpV<3A3z+g&=A^#$2Y!(wP+hwZku~}x?N+y7 zh)ZOj`Bh@9cr%e5VvZe`DxQfXA zg`7l%6Kr$#t7B4VGMFXJ2{eDfN~9ztC)D7IzRI}%j+>9R+m9b>>xT}qf4Dp1>|?Dr zOGj|sljqO13(TCK+BnW09WfLwijn(J-%+}G~>>}T3%KmL2|f%p6@v-VaveFz7Rln)W(Rnk?qrVZu$ z@>_>Mtxw8p!kndcCl@C$`~vFt{J=kG|NNi*x9w&3-rtVh^Tu}iJX<$*hO!N1=P!MN zIuIFhg-vxQ2v?qlUq+7lt8Zyn){H!_!s{2HS@?J`QgCQ33>e48C=+dq4bf0`^5K=L z;tRaciPT|4i=-2hGiXK4`y&IlBn4xlzamehg}jO8`iqS6TADL?>EEJ>*KA41Mf*g3IzXNXC+{{aU&{-W zMXD58CJg=UJcMPykpYl0=!80=wi&$YhdGc?=itp5St=tEynIMxe5oI# zW5A{*I#_+AXHG1#t5Yd0Yd~z7kdGe}>%hsa(keklWp*xACw!sFSX>n4dRaJ7gm%UC zxv5c>t?P4TqBPkWS3e+QTRHh57u5l(GvxxG%c&dMf!5?znJY^hwFz+&IdjSq4L5#{ z&yuG+2W+Kh_Nq=z2ao)yK{}>JH(&U(bigMadBoHv+d!J5j~F;A!~38MCOV<(>H$okvLXz^dM9G5?>O{c6mr}@Ko!! zy(`SMD3rp>jijAhUSX|!US`B+V&xO}z`X|zy3!+CB=UKBHuDC{KYb#e`e%_7DccRx zSmixQp5-Q<BQeR6YjSXP~o~@g0&nlQ_9KimHF6CY`he*RPKrdRy{%WbyuF(CP{ z>IVGW=`g7J>xPnR@Q{D%Haz$9;1pj_K?huU#&f@O-QuH3XDFY8M{a39`BVR>ee}0} zt^Le@{BmBfy@568PJFDOm-h}Ib%J&Ud+Teo*n_UPfi$IJq{nkjSwKdjXJP%7!8(u- z#DNLVsk4!PmHRf-B;rv7*bV>~0%kUo>_IzS(K+7F6;kxAU|CrA9$xOIq#0~o_}!;D6wH*Cos1ZkZMx1!4%tt5=L#l}a7?7FDP z=~XxJhyq~4Z)c#a;!@`nhXO|fO;igj0%(gi)(=9M#chXVkL}(`)w;pzQGU6 z-8+ZED?f)^$~dZMg(U!6`nv-WHyTi{5zo17s3BA33n_c5xgGS;k<8>rX9g$^ID~b$ zd@Zw~QM^m0{z}K^K&u!4V5maEBm0gEm#^9d$>czu*09Q8qc#gcw7H?_>nB_L2+^`ApoaRcN$P>cwUkHfDX?3^+8VQece zXJVU%4IQADm-aYVuZKCm6v_}SUZAUci(m^ zgYw(J0^)YM=SQZ?*#`){w(&83UwZgxd()5oc>D0b{EhaLx8Kta9=)CCB?t4+AjiGRtt}erz_ok@ zoo$F0{z_lvC=poqk-5%GSR@x2*t?(lCLcSx>F)d6-4DE>eePo)Zm)ayPq#;ysjp>3 zi(C`)e1<5cOHf29XB$kR#a zBqJQ;0@REM-A!VVD=}z6?|1@AlUb3V?^JB?4FMQo63pxEG}=D(9@doLtDdj`06+jq zL_t*VDy5~>MqC@quHnEOh1718#ZLW3%>X_bwi9PBz2|{lJm9!ve`X5$jm^aYPdWhG zW-F8pc0yG`VS$sZd@?&qZlC}qsE#&mhBSi>IkMdzpo}b`G+Di4a9|TMGMQ&7Iir;& zZyJ1(TkN|KE(0=JWjg~0Mx|a5dc0&uC-vN>n0a<$0?m=GBdOCs_S(~GI-!Iv7RjLS zfoYXly{lukviN48AHS>pU>?$(I=dM_EFGK91%2daIYp*aujC=5d$staU^=C2Tn-S! z3R9$sOClg;mPh}a=U?GuKt@7tVIgrs5Q&*NEK``6hH;gy`+Eiz3g<}Uk<5dI(>Hl>d~rOseka5&7ib(UbZNTCVD? z4;|7*82pnRRFOw0cxE)IQw{w;$;h-twOJw3puZKzr($v+cH*-pwrWacI}FJIf=FKHN^;bW>Y9bfi7>$ir>@z*>9F z{omhKpL)7I^=BVzmyRCHLq_R4YK9u>9R|2OB$R)OX5I|K?BI$@|~ZHqK)gzu>6be&)>m zg}^pM{?-rK7C$|qDD5CZFg-$PWd9^~sZ_CnvE$mEG}JEKe!haR1BPRDBH;?O9U}VO zQP2o_VRW%VRA{=W?VN|w+n@kI&!9t-Z+1?lS+@PhftV!$10@=DsDQ5x`H%h85`??m$L^jtFXW= z-AZ5OB%~naP3SsTmfhEkD}HElEetycW6>uFTHw)jPn^@vkeTGc+eHG{484%zfGX<6 zQTzeRGP#_i$e=f?;-OyTB`poG@|oT$+@h(xlHb`Te^3Gwy$5NFx{vabSLKl$RU61$ zl@^ISI1PKs-9Tf;fcJYKFi!aiz3VwK^p^ma@l+UK?JAz7TWMgf2fn2u`f8seQ+-^% zGjE$=(X(iv@&!UMQU*j!g;Z+@p1M!vTio=CSy2nz71;(z^1c85pIjvWW%GfR3dsNl%iBo03HFUb7i- zS&?4KpxkS04eK74J2Y(E{W!Da@BYRAu)Y63`7hf2tfRi|Rj+GDZ@as#AG@VpK5(pU zt{%v?ffspk-~ul~UbsM6ZP2csX3)QWxV`ihuWFZgQS<<7(N|a3+B!==ToRL|Gm)!8 zlpDHVa&V46e6F4bpx`=SS4Wr4**4`RUvR8ENdsf*LO{{-u)*AT+hFGWm9KwGW0g(& z%Aft4cI+@S*WmhvQ+MBT8CdFLU%OTe(F-#|Ms_mX=a=pLv_ zX$dt*a4!(%VyY6itk!i<3*iL&-C&j&oJ0lYDnw% z=ft6Coz=>$k;sY}^*pUOfPT#y(^3aC2Ojm;dz~&S3_?>n_cXaR3rc$M55;`%$t?{@ z>?yes=ZO%%P7f@_cRbvfip?RYhSkonbkM9ZKoha!GKfe=v9ndt-eG(Aq8ZGUQM&g5 zqv@St=!$WvI2RF-no18PaD zcR`A?W#->L)-9*}y1k_v(0 zi^3NoalwA-&olUI3^hv|JC2j;3r@RDVH zqIAFbz@Y|fq_?P?oBU?K%JbX`&qo_P2)yIRf4+U<_kXWF`S2Ip;lpbz*_WRK^Xdce zp3d~Y{H+jqM<3)VoOC74BpwhSHnIufq3t64R#~`zfrpD5@4CO;_nHUVr#|%SEUi%y z9uxNW_YP7&VHV}Bx3tk=8G&UV`~?NfDef@4!)!J}uFi$k&p~@V!MS!4;UgF) z--o1p9k<8pp{ZIN5C(SH&~==E&k|8=wRHm_!br^gj0UH-|Akosg3Za)f?_bcZm!#P z-=5Nls&6mQsV~54>&-O^RcT9Q&$IFtnnC6J{gd?GY0m>M!~?Yschn~;5z*1eJsW+n zsdx2reF&iO#+h#((+*CGS9QXR>|$+xufKJ@QnHj&eMeLc9PNO+c60=pd9$N)1}JTO zj|)pz1&2QH95+cCJ9rH4c4#9Q8>wZyu=9(YfnYF!JmGgM%c5W>BK^ zC_su%w-RAj+$5`How>ocrGb{*PGjftBEoiTbm;0tJM}V*jR!_6^KDZ)oE>LIU9%Hd7#_~Ev-y57$5@}!!W=fpz_ZH0j?1%HW|^~-K2>6L-# zMc|vn&%xu!t+%v;CwUnZdMk*N1-3cS12*{;0^KaJ+W%eo4DqwgA-I(hnxUui^W6yb zkP}yz>iJbsUjVhq9{SRswd3o|lq(0?JiN-2 zbeKg##fxv1Q;;F>&-lm_)1X_T_Q>m3^)+}6b$A5y+^6XC94Jwv^iY=}CluQf&%tAn zW&PS!B)ns<1)l~*q9LHJ(phvm;90ns`CS`g1^VlQIm~fm<6fKduRfMKbMsaR-zPgA(B%CI?-$3bZZ8#RN z(MVevjr)?;n7#69+cLE)gYAxMX9;fUVRjl-u44u|Gl%M7H3uz?Bl!NznpsNL%Mbb? zGHd8%mRqClk6Bj9Fnff;7^(TXw6W|QYN!ZQUO<<32CX)#T~J)t2SyW~ti>g>8lW8w z{VaNlJ)3UI$$H_=D+IuXJ`;m&5pwH4dd8Qb5M1^Y`Z5lrD^ZwE=ts?Xt7{1mbOz+skV9Ln_QVFuubYKJ?FI0No(L-8wh*PUusnKfT! zdB?ib*Sr9^wXu~QRNRfktum*9d8{v6F7oBhi|l0{&GjjVGSqn*5Ks6j+U-_Rj*Gn= zT0@65K%2;NuW?_@JiBqB-SVn8v|Dbut^L_YKgbdrmoS!2?%*SXJTRnETYqXQLE*CZ zI_xuP$e{ibgL3NdrTypgP_TOWAa!DemE=4O96Q;5Xm~FJ&UX40srvnq`ba)=@!3J}# zU%mYgYkBjL~g%>mi0Qb<7wUGNls@MP!h-% zJ`IYM5)B$(rzXn<2*}Kgknl98Tq|TIk!Z>HC(Pa{0jrtML`dWxe%^oSGNppwvvaE%py6cDS*n=up z^@vY1hb4b_Wc#?`HT9G~`k{}zE4aB|c3E&%7JTc7Yeu7mKUs^-z`&V1x72eGhXVJfuQI!(WT=Mcx>OmcqmhmQ|>+4iz>h>J@;N5P51Q&`Ft-SiP?# z6_S4>?d&l_77r1R0jZ0AwB%UKxC;pYoM}Z1Lq> z)|skT?)Kv-XFq+I8y{?`flJvCAB2zgs;#x9Y%8AXQ#w05fEx#r8F*Tev+PAOeIO## zs?<;YOOq^tfJS}Uw1cx{B`?cefc1nWG+U=gyBq=5{FSrBwkFm612r);oQf^Ef*nf(vpc31mvJ%u{)|ST?TN-V!Jq?mh2| z6}F9BSv%a;Uhx{XQ*`i39pD~YMG=eBtvG#HbE(8dyt2>Pdu`$D_NVFNyUwZK@Qiqf z<3lSfJ~uCA>)AF0`TN{DGHE&S5Oi^+z52~R(jI^6!S>YG|EwKiuf^EV{V$H~2pevq z!I)9EY}L>DL)r4sXFalgscjD_J9PIU#L~D#J;@f1*mjN^|BhFFe|ybq-_Snwk>75| z(0IQCd2nnKyfQo-3sMtB^X{P9vX(!E-VHThG(q3;lIQ%8@d* zoO8amO6K0VCM>fStO~LCp*i{wb5gJ-ggsv=UlWD)nEYMvz!vKrANuB3=?KoYzx>lr zv~#CV^HVSb5gYm36r)bf)~56670w#k(MotQxfl!3-b1flZ*FS>j48XBW z07FiAU3n@|AP2uLDQ5xxmw|~_xfIU-&)%CpYj)iCeLZvcJNts200BxAA?^}EFAXaaSfjJm29wZL;yiiR+?ltk#l!C~;#HbI?yGhwIou=mH{34mb<6mkK&80SBtm1QlMMDV{KdhD(>KfI==(bhs&> zp&R@Gx8_mQM8FhO?v706Oi|eNBGyRBisrod?5uG9fc^b!*&@t|h(2<0V{kqMPm$b5xH#W}r z3tk^VD(AJzveN0Ma8|~=v<8A+eL#z+?hC?Z(Bdl=~ zlocCxT$6M-<*j3Ge3KVGU-Ra-wY~dR^MdE7*L6b-pd4G>FaQ?<5I8Y^!~r`I|x8 z5gjykBtF~4kmEW$X8B%ALj9k~0DeG$zw5auel4YM^Ro1Js5HkSx1Lf=|wM;3M7y#)O6~mG&9>isi%NNu7b_cYa-Tv zi#GM!jPR6(06G{XCTgZU&<7{dZS)MIo~fk{S(Sakh9#+%w_5;)A=lv>>}wCq<}#)% z;F!t2UMMXr9nIo3gCi+)_EYrca?yDVnBr}T?xk;025k!DgqHd_Tb%&m+ibN%Ce-H}wj?tE^FV#DJmQbAJJ*7na%&{n$^mk9_EZ zZR5^~*=P3pV)l#iKb)YMWg$ztKoQ z>>YxghLqA#q)zq$y`xU5DF7x(KD#rJkxr)fFgh87PEoY`?=T|r8=Z3Sl(h?yjw^Bi z*TZNlA>Vn_pe->5DA$KNWID=5vVr+5=T0YVLj?(NRAvVXsf{ojpU5h56i_YD0Q>whpt#a*1Ab7v$2W3JldP+xI3o{dvP%_G|;LRtEi=Jav z;d_R5l~+=$?~H6Z<|XTpEO6SuAK=4zmV?+rm)+{B`*D>=0+vBJ!#PO(y|(%n)j4O z>OVlSNRme$NJb}VMsWIIJZgmY(OHh2ARH5hVb=z`)YFqaY-EX;NhL;58|O7a#SA&l zDtHatjmsMr*kJ*x(z^Q+^Wu-yYQaiIu!hwd>}g#~^B)uJKq=J#XL{F-NVSW~Vu>rE7^TC&NutStDZ|&G zH2BM}=oeV12tN~Y+0&G;b1kwV)VZsw6~qBQxids2(Si-%sz#HXVgV+O&6q7MdkE5P z7NP73mvJ=Ie?iL+D6A|Wg>{>RL)d=jiH{=ieig4f*uBm79q z(g!*F>*7dGfF8uF8%&)N6Ng(f1AvDxzgVvcmSbFUWlcUZOxmNbuV4B zA~$d>AAUMdb;e1?(JT7`n)IN6ArJqINIxawRS(6}G8at|EXT=qgMsQWJud8+A@_-) zpJ7iKgRe|gCRGQNe{*B2{oq@EnC}Pam}6!7txe8xP^rw>O{ID_P#F=z=Hx-T;je)KKffG-Vh|nH`d5Gx@aAdBa=Y#uAlt6L_j$ot}LO- zi>us-F70n``H8>Z{`{jKZ%=>yp|)?yn+~3GfLH7PYsE>bxOKrkJM5%y=)C%6+N8-T zt04n>oFm&D%Um`#*rnxlZ)}(ZYtD0qfH$#T3wqrjFl2q}!Sd+8I%(hM7M>TsS<*5za31xd5?&lf_0NyP zsQ;0CVnR#P{%X|!-O=ls;pv4EofnrTgrGjwbQ%*v||I5RK-F3jMF zUjw}YRV61Wp-x4oXi1Ojo-dHF4*oSLcf3wuLI;?Vvhss3jo$GK7g*Dj*+0`5u+*!h zDjK1~05|k4*rayiA;Cdh;R?1ghklb`G;pPN>3I#Mh+4QM<3yUsI&z+Qj(jFZkwhZ1 zr87-aN^1mMHEYUO9Ay!%WV-50fy5`I$n903;5bfoD8Yo)a1QEatEqD+)e&anN>fhM z$R%Yk1#s5!?p$C|b;9%5EhH!a6m1=#UZ!eGrjPdxC0p?*Hf4RwOj%SiDYggFq%vlo zr4T9sGeT)F|MLY zUA2A{g`Ab6D0-}TcmX8KA%->ClHXzr$!&Dri_?FJB`k0J@tD7(T~?W$SMWURopHOqwvcSpv*r-tiN(gL|$#rLXiUG zBGWWaN`a0n#lNU4!N~0AeyxPrg<^(G24Ai-mD}UQ5TG(o)xwF_7IB%ycopmH>q(jftIyyn(1k-%2dix*>!j zwlYP3)1?HlX$(zbay%0RGD3oAw@FVdNRO69ZZC|MYymBZ(u;V?tOjREs8{yx zCO!imdfKegtOs=)s>3!M*chkC2hjQ$l|<_LT1VoX_W}q#vJDk40%?=IzKsHynl;t} zfp*7is65gxgKBvVA&Tg@7A|E}2;n)n_KP|pNQsP|UCzkg$!gVv4p;h^A}11ic0Yw@ z$dKT9q!$r+$<1J0nMhknJD_E#O@SdAIR?Gdlaj!`NT0JD^G-&|!Wi7>U+JUHn&u6b zFMU=YiDNzCLuo@#N$W?QA7!h|q&sA9!8rXSOFD8h#hLBcTmpw4;B2xh3&38b zFVYdV|4^=Br;=NFh6v;-0+$0QI+^WDtBRSWCAExa*}BRyk}Fp(vun!tx0k;3&i1)a ze7NoBgG$cw+qJ8!wOTsqM~P?o)v!0|gGW_M+N?KYq$`7Nu$wk+!hGxMmF(+&i5FY< zxu5!b-rxTCgTLOcUOvr}T5Llb(`nYu?({N(v?jli_zllPo+-`_P;H=9XK9W&In{!Z zfwb%x1-$iXl+zAc&-Fq($m6-fKOZ=V+lF3zA(bTcdWY;9D+cRIVZYrB)7rHQR5Msf zXOuvfHH|fbSHjg-Zae%C-ZiVGY<1C!raXNl7CY6|a=bu|-)fR%* zH-0_}w*3UJ(j1oZ@1+s&M#!uZ7y0@fd83mwzXmWctk3>uV1+Ajf2L=BItVt&)gJOpxd*szDrxa!4Kd@<2YLAoUU#AzQ0`YT@kNg-QqJkrT z^{C;`feP9BFEdt>_74(u(;!BrWt&7txCtFI07PyfV&^W8{FFa`(iFr%#PU(|c?P&? zGHKA!VeAut05f2AUDe*}aAJh_v3F)YD}gw4lO_x4!ylW3J{i`VP*X^jM@g$*S7e~A zbsfk>a&ZVJ$WZSXl#$6HJlP!G13KF2%#dE1Qs(N(b(NIz)Ut)xG5pK&`OJ8v)e2RE zl{ECBv6Wd<9e=1Lkr_Q?hy?Idk1;Tdw1?S6UuF$Q5iHNtJGCTT#|eH?;Pyl%Q@@o~ z4Tf#81aX#*#eWCKnbpXxlhoTGxQY}US@CoGLhDfYN?Ztdmh2>xq6DNS&lmjWxZzgw zaxz?!pd8p)V9_nZLIhWv6094fjk;d+rHX9o3)IF{mUJ+ks=mWn6Jjej^i__0{}G!} zgD7z6MH{_Fg1qFf2|QaFwXCzI@+o^%mf6Y~xyXurwOK;xD~v4CH%J{mZqX^Mw5$BN z5)l0sh$K%j_!=f_RA6m+6E!FKMO4BavKsb|T9jw}>mGY&^ z@R6__fB@08-_i|N?0yLWU-kC)wnrZLN<00)=i7mOi+K-1oh`3g##XvSC6?Nx9+5A- zs81xc%x?0Bu9IfVH(N?Io{xZ99DAc>9kZ`b}P{m0s0B*PL4qLNm-l z0x7C4sFRt+sq)6rHsaF?>p-WM4PZ|d;QQQ+ofO=s>0eO zf;Y(Aus|%}aN?h93+?kVYeDB&9fKXd9kWAnT#$6g4m^`i!V6(Y$S)%?6zL>uz!DZ1 z!H7+r(@rB&)S|hTU`RUg8n^@yrzqBu;ncxjA(LZNM}kfc zC#Uq*k#Hb3t*~>1sDGU}x=F+991}NR$!o9QN)M|TOIeh6+Mz}|5*%1F%jAHbK_{Rb z2e9UgSdGlV>J@G8l4t#!$oHg0l(<(T#ZrM&zZ|5aPfLWq!3;e=Dp zORb|%kkF{#;Ts*(dyr0w0s`)fIy453^eQgnAzg%!D#&TeVX7R$fWt2n4&dbJQw(S?r||GbHuce&UiZdw z`9OQe`+uQ*@?-zDZJqmiTUzA3Bie%&QG+^jk%;|;JfP`z+}|#rc(^SuF48}j6I^w3C;bQ;NjgumW9z91bq#HKhyS(b^l$bX z<`1}7t?*SgUgt*>O%fIy$+Qrs;#Ay>-%xUZvN- znb{z62dH!%40L21C`RKQgR~C8Ebkz~w_u?|ZyX1p6U?9(-N6!$pXcoL3rsrp@R5_W zr=fHJsMD%z@`>cngdU9LH6qLLUaoq-n~yJZ}eMpspsz;!xPa%HdJagglD z3$kT`UW1TMfT&^Dx`|RaE=wFs6GI9Jof648V>dfkX$RYaRAia@o);_S z#kI>;%rL))@HbBK>~e&XyMRXhPg+pbU`it-18OQivU1~K27)@zJ+)i$ig`GsZgF-= zn)PjBuTDSh(6}_?AQs=!qi9Jx^#FF_8#j!l5^C(vL-J`bS@Ho~$^#o$e;|zb1?!q` z_xCRS$P;EI)A-(et1g($wq7l|$tZi1gtQh<)QM*UgL48@y>y9)F@= z=Ig8bU-I&H&+FdUKKy}y$xbTF+D9i&+>j0VEeCCwC#;<>nIQt1w7s%l@sWn@T03@g zME4Dzx-1>JwY~ODKhi$+N59jSHZQP^4fWsgB!+_6Oi=FX6=o8RmH|TZnw)O0*;MUS zHmkF7>*q+bo?QDIVvl}t<~(qRVzaC!aqTdJGvZ7VS!Z@7X!sXlP;Bj8^}5?4x=a-^ zRyotO7piLYBrlXo@aq9-6o`}v$vKTBjg^Zo_wI?mD}vu3@*QpgD`}iS27)-IbR2G1 zSl*F2AHaO>6D2=f;@;Pxv+9A1@i;Yvjww(PR?}Xy|WK~ zA%>Xb?4ZR=hdQBWx*aj6cDki}c*|GH5Y)&H0EPG@W?UB6?^( z^H360y7ZnE_*!(3V<&Y;UgxCT1l6sdbc=Rm?RLniqXdCNFa-^+2UEJ90)>u)TiGe- z0q%p6gLnvQ0R91tQzfbuggkM|_POLLmaM!116)O+n=&XlG`P1^-N2B8o@7bYd62q) z9U9u52Xc!i&8}s*O0R4PhDd`#=nmTQ8~HUn<|Z-FVxW%|fq!SxjVM7_c~HPf0BA>0 zcll_jj+1<~Kw@vQk}h6qNWq7?T&$lxAfu1q{gYg=1&4I!P_ykF7}`OdYaHmXqz*ZU zKNGvacG%O{B=l(6PE-=YFbG{P;&mAsI!bYpC4x>_E z)t5ibi9Vcj^AIf4DEpq1epH16TXG0Cxq(sH&=!VriIgN&1Fya2N#EYP*v_+8`#rC{ zueE*q+oPZPXge_HcSx&Lf~*|UlNaYixiYP65iZ{q-k)j~79&whdsEQ5LabsO2&->p;}$>Bff~Jt?P3w?qnlilb!xmx@vZr}?u~ zB5CQjGkNFLaL{e9{w$vnhwyc2ccB5Rx<1ZOHm!n@{+pC>WloO+X5hTY&+0ibSZQfL zN~g1Hidmy~E$pm(UEtmzM%l@1_r%{d(Qgp?jl5lbY^o5 zaL#}`BhFe?XU3h$aNV8D6Y>f4Nrz)AvIDHa)3~v6j7=AiV{dHLv6D?>OIp&)NeLwn zy%HyD#2HA&pi(4@wlHy6CQ^{n8+Y*Kot6j>5*#4oIHHvy10p&t@knZQ(OoR)13+-U z;bJYGG0zp3o=($nu4-TkHFT1z@SbJt+21sj-qnc{qd&Ee-?IeA_e3@0NIecZs9H_`$ZXcGwv8G3RmJp*64pm_xh z{D&1P%QRypd%(OL5%SELwSn-;UCy~Ssp|nFdIek^ms?cPc0S~21L+2jgGu=dr=so6 zL3HYNOMbxxp`@x*Nenf__Bv!q)F$Xs4qIiEYRDk{z)7*n!DGsu^pxEcK%mf5@2ay^ zUed|VAJP*)fCcNd37TM<(|IeKupL3{!8>@;^M}1kW&r)~KjYTb43efvYc`AoNO-fw zKQd4kahM?)_&BJ+u^%B{oao`XV8X91dmcdk`vXQm(M$~8@YCj1s>vw;qNTf+K@}%| z+LR>~EGgMwuk!Wv^^{M_uH#5Y(fqiR<>bXETZK^`-!sWg12j`cq*cFAu+p&po9gnB zIhJf#e&U3HIVBjeo+eGp0Gzac^b+2i9O=ln8zOcN9{B|&u_BlyTM#cNeF`OHo}|dQ zJ3Ltu$n`qQd+vMZ``VxV@xN;q9{Y0J&(@w!k`&J>|FJs^jKVcCMJ_2<2z&svQ?-YZ zOT7$eMEW`}T-^7g?`@y^{1@BFFMXyRVV4#~l9Kr&%*IEW=c%@ps;&Jttz(5h9r=_` zY*;Xpqx3ru>0B>N=mYl*8MNQ^!i?O>z8)?a%IXrr`dKH4(^fq1nAC`Et3Jfm)(yv^ zp?z*hN2eE#FP_81gwwJv;U^jN5yYzz?TTKzNa{NBbUj9AKh(IkPFx4`M$!!n!~)Lh zJ^s*_+k^N2RXg*<Wfoog_B^kD<>xBF zs|*zLj7D}M3Q&Ci0AQxu{2Oha-oxq2a?-?34CcM5ZzcQ8Gm? z1Xh2A3{G+_0f9W&CMlC7;ULBtfSR@-4BO?RUyCzt&^1Q*bpbpj3crkDxeBB34?T&M zNRW{~u-Gk79IBjb^Ch!%qC-&@o6tF_*Wd+iEqjp?Ix!k+&r z&UVVW@MAObQclQX{n9e=bR{p|pjQHu&~N_Lo4n1=asb`kOBuJA#d3!S^yCPjU-Yn&56!hzfMid1d8o+P5hJW=y`>~(7 zxsz>~$sujQ@tX!=23(bWP(_b!v57DN8j#=-Hqdm*c(CRjnYPtk?UiWN1KTBiw z7d&Vby$)oeQO-MHg%dw#>2<&YkFF#7VmVh4Ro!`p%)Jc?V;$Yi;Kl~K7SgCD73P{k znJuJUR$|u_xIySU&H`4I3+J9{k9_4+``YI|*&cZORQtYH{{S!kEVW}t4z~k`54Gj} z``XHZ{cZo@!wf1*Z7-jdw##txW)Tn8FNW@=h+6u(KS)7(^Dx|Nh8?-a~kguH0(&I^Tgm= z^w2DJ=`=wSO7xL7 zS*HW^fG;PjFfwNqPenNl=wOzIktBDV^=a3JHOP+xNTP#N1XLza$4y1u^gxd?EZzVF zMtaIv`h=@V;Vx^fU2;p?bbr&pRZnnthdc+0yj^cIyV0a2AL&(Hgf99{WQ*t}4~fwc z45HRlW#It1oTY%=(cL`%yyj0|%HIGkM@KrHfLX-LF><~QNSvpxM)CxO98^O^gv7!} z;$EhLO#~vLLS>arYZewKS-X=hk6Ah76PiRy`m$SSkYk#BYIa*(b?fCXuC@(jc>@PP z(GOZ|n1}iK#)1|022EJ zUZ&FET&OwKbrCi0GN^13369rQSF5f_8{%nWDH+h{&T-CLyZe=IXh**OVEfBI{=N2= z_xR2$)DLIhO{*8?4ca2Bvoad0oQy=;L_LiUgFImz;d4x{LN%A=c1uN*tFtCy&!IAJR=Ta^J102brI)h&m zteG&CpeOFko`KvMl~w|$!FK{NX!LWc9dZUvcq{6QFevr3PR}TSZq;h)SUu)U{tJli zqGL|S)9%D4(hp|W<5y^9Z(r`s|j@EPK2%z zM-%!X1~NcC#*|kDGWf5-!aIIMeIr(x|oaO`Dua>}w&QLiuyi&M(&^R&N|+rwh}*fx=_F5wtmNf9 zf=rW_tfXpDgGaYEj4Z+^lkhOHG|Z1ZhyNWymXK*e&Ga9n@ci> zl15k{e0t+oM3vuVF51{SrWA3flP8{~S#>vv(&azihOlHyC77IC3Sg&J> z&N|mJ5lWu~9c30;2V#`fwoV~3si7}EZs9muGxF{YX_gk;$df*B>WE;unTCJ0`D#1T zs4@@_nfSm?q~f3!^Wd|Hh9lR=79N{dAPZgkRlm?#+gD0oHdDIkCrnF)P)Bei9=W)J zL1gNEP}*alP#N6?-J9hxh2rj1+U6=cU*i4K*SzgL?Kl4Q|K1+@?4Pu|UjMds{xbbx zR*GR+c39c7$2I8a1wYFNDrrXH3oXlUn>i5_W1Siq!Jm0 z@nV)S*P)p#%6!!S&N|?oL1@h4O78`RS#d1MD$N&2^gl6rnTlgN0(K@(eD(hJ=!x@fi%(Ct^~-Jh;@Ng^Wrc=7 zQ>W4`>^a!xc^PT`@>)B8?&)^&tBI-um`-5NCb=p+oJ^p#yD| zB`_}9~N*?0F?Z;ktg z3@};e2!>VM0ZllswjBjH*WhK0w9qzJ4tC@WIF8D?L9CT`K+7zh!wRsQuCzaJYujF) zYb#63%vi0(A&;XC!?ZIQhg``}P0jAmm~s;*-+`0=4$QR!wHy_ri~E#|Q*^n&p(OGIsjfEwkw) zc#|3WIFBMw2~K3Tt}3wDDj8cAo#NH&@E{BiDW?4M zbr`M?h9Xo5+KAMp#)UjNC&f#RFPOsnGl#S|bkbK0LX|l*trwvo&(bqAO$@#iEHnA* z#X-;dMZ1g5vd6s?!3V`9B<^@$vyM7=^$B;M=(ztqoAxn#>H<1thA#G!l?PKyGai|( z6OtxX{>p4w)lE&nLjNi@axHImr1GW*K0bw*hqhL>rp}Qc+6Z}d-8JS=C0`Zy;xtE& zIJc%HH!$f}goA&Ul32D}TQBx-1Aqbf>Drgf-pIpVEmDSiaoo3A?=HzI8!3UKzR*d! zDa5rL$(5(%^dH_-XxF5}%XZ@{L$+?e`9KaoU+$MCav_s_5XimVf{#r$m^Qz%cd5PW zC;xu?z%T!A?fp01(pGQ(-gfaKa^^-x0r`9or`)DMBf3Mos)2G7u(dbG_IC;3q%Hwu z-RAj0F*#Qh&KbhK)# zMO_w(P=}p#O|QA6K*Uyo@Uqh!?PwNrHZA2LICj2XsQO!Zy*L1~juiB@cqT_5B9tWY z>(edl@64kT+fsG}6;`<<8~MgIOXl-9?4Dz8=P{9AVN2kN{0jd(1iXRrx7`AC&0Dm9c&4rHJJ9wst2{Tqzg=7g_rkfh zcII?jyKtdBaOrYeT3ToiKJZ|>pVWDGNg? zX&E4FBo2-MI{4KiYiZG;ac~ebJ+or&$`VKc^Ir{18V1vBX#7lacFBQnC#L+67TOdCD+M3#cdQ9Edt>R)dek z<>1+94h5tf(MXAkw)wN93$NpJGDW9o4cL;g(~kh^X)Qsx5!4R_t&i?=9g{~NZ3<-K z;E3Wi2j%lp$7FV(9egXT*VDm=vks4U6-9xlWv5)Tfy>eyxCm3c@)QQxQdbH(oSX-G z&vQ4CtGwtPvIrD-4;G?{!f{SKxv&@MZzONnDw(4TH>V-nC$}c>}*JJ~8u1e$kwF z*mRVs#^}YNB1Wh6c6U_UHOG9;-h}p-!a*AgWQXuGitLW)H>mvWumH0k%>I!)Wkxl>QJg@vUIK&z{(%!c!jam9~NTwzC-rM7ZpKTBO! z+urq?nRs7oS1zAtIn18+)Z>q&$=&}~_qSKR=GARK%S84cXZ`w-Lv8=TLv4lG`CC{T zvwrdE_LaZ*^R{;BQoEOR?nm~m#3{JSR7>7a_iK6X3=^qpsFkperJZda!O|$=6d`bi z3){+!^N`tjpT(LEPG)7nw_Q1yc=pe|q0_*GGgiGr4eyTpnN72XIQa65H{i8HlmmfS z*M!hEx3}HEh)*DCyyz;cO1zfF=$-;0)JzBg_&P{en3ch}{#7Z%DGJr4GjC>TKkX6f z%BI~sR|3&QLS)dM4z}`CZ?nZ?;J3fxQi&?P4n#J zMWAF84hF@e0Ob@{3|@r@c9+YWyUe)DS0o2&{wGV8B*FEh(21tV1@b?6>ZMJ~iHWlt zdCR#fV-Fgh3pWBoU{Xxdibnn1X_s84^#DR%evE0<6%C_z<&X04i>a|U$z_(ZW%1=` zxsaEq1)lw(+~iN?g4mrc@(s}$Ijc;pFP354u?Zh($gKi&f@G>ukLG&rSr$!YrtV8S zvUx{66NCvI_!By$71R_~n<|<0uI}k7FU3BHq;3HRzS8GQluRJBEhYq9GJ<^m#2mUR z@|ZY~gAS}LXfi|i)^tg8m=eF`8~FRp`iuG)2% z`#Q!b{dP~UU31L!8u-RRy*X}$L&odKzk8D3p!B!I0?bnLF^db#R4i`cWK*#g__E&| zgM%HV!XEs@LJn^`g}Wi`l3v6e&u!v+-ew`xM4jXr!NpQUNmmKrKk7HoEZ1}rw0~NQfuEvrJ@kFn|vFCS$PQ7?{dYLsBp3E9UwOqL-(E)Dd>^lfQGe{=lf>j-C zje4esA4Ij2G=YTAlul-HLtg;OuRCO!$rBmEp1OsKxd{YSvV@f}gS~nuRe7sy;+&8s zTxgkLedc7Ws1zMxGAo^oZm__RPnwR(1~+2^FEqJO=gc4@t7TF7Ac~a2kyh0t>!LRH zDs@K~uH%@9Xuf~Oy(ALb0s!mi8HX`N1@`HvcRR@fT*p*1W+_aOh>f^Eru$Xw@IbfC#EpU@wPYR%d$Cg>|E_T zEkmtHLPy@vv7JzvId8EZLetOc_EhWKQPgzABK*dRZf<63_j6&XxEcx zfxpFg^aE$`9oOF?nOy_dW*TzTyxpNNpfI9orf+|;QA@Km+Eu6N_OYmT{%#Y((I z14wR2B% z?1-szOAbEHnlRc~H@Bg2@$|X0tIMZw*6o;=7FoWsuPrVwwDrv`X6AVe$^duZ0D}(n z_bu;ft1MF7%WU|Wv!~m^qx;)KUw^#)&L4cJ9a=fqo_gfbwg(!=Za&slnOQ$~`b;}^ z>(REjvcE0QFGGVFetC0~u$h5iZ@a>5e|$U!RLAagpuRYii|Pi_Z-W=P%j*f8-M48D zBIyi>8>(9;MI1+1&ZargZ}w|Om>^F&Y!MB!4OTn`5x+ zLgqoNj9Gb%JFpuhYiAvJ5b8X{$T}0h-t4-Cr>A7bmG#;pl-TZioqFhV9>}i1So6w%@-&P zf$~3igIeJQDjCWi06=v(jCTsD1f+7Aats&}PC+JhW0ZuLRVRy9w1|9WM4T!sX<(PU zOJE$LYOmWj53yi@CM?}7*}Gi55guzzzS8rwNyk%qBEYNRy_wN;9wdt zk=)FF3S(ZB=G>_ptEe&pi&Li@z65T0XZe|ABE08rw;-jIqf^+CZAiHWM>&wSJF%7P zvD1K7oXk!ud?8fq5SDmEhM;Tub;wVhKGoLN*4iQlvwzy8kTUz6I!GK|b9|cqsV7hH z-pLj0>WXmqSeV#D2VI_{77dd~qnK6yY9C>BpJQp@&9~mto_PH6;M92TGN{sB>f20w z96frd9X)oe>P&7J$`0yUTPBVdNR>fc%}{4fxsglV2iN`}@)k(9LBNBnj=?9VCO5pI z)~N$C`g`eTH|Ya5mk+hqzwO=am;c9q-hTF<{bDpe{BaFer_>1A@~DGy0u?HC0C90ovAZT%WN}N^Xhi&JlbRyXP!>nPTiNN>_2^P=MpcBUU-TJkaT8epE#AB23Gd* zAtRQ}i087Hv!~s*%sp+lczJYRyX)qg^D^r88n|aJwUdv1H8%^7Jor$1_@S?~`IUX` zro%V2L&xZ>d9&%(TW-a1Uu8|?dOLFSZLHy3V32WJGTRLDagKwdlZk#hqM5-#nfMz> zt-StfzhpJ#BEM-bQT;Jeo0X1VC$exjp-m0Z8DP9IxHgDKaAqzYy3N+OiHX;BE<@iK zNF6R$z2g@v9E5Xl1uO%+X8`jvu7YIBBg2ysu147=!}9V^TG6@WsOvzBGzpS`bcGnu z6kSHsLn-_-xQ|q5YqNL_GR?nr$C3fF*BKEr!v?LONEFyQ7=Z&B#2rgKR!}6y9iy zy>hbwfaO)YU|1d^clJJ$v2i+HsJ z%+eCRQ-D{$UrJg^26^)?h&=qayhCmvVprqb7#)51dnBUw6&NZbY&zDep2RKW#=`0DpwX?o@nji zt(i?6Z)Sd%_-N0ib)Htz-znl~Ic0C(qhU^;NrzBDFUv}~)xlp;58>VhD!4%~8kd)s?{y#3XG_;7p4 z%U{n{v%D+BP0IKfTV$&{(D2t9^0gkwGlEC{l2qD}#x}uUoOyW7A;bB#?RMMCUfJ$E zbFzKrBOh#UdCxDjGihVmKh}`qvj*$_2veJ-7wm?dqyjczXVbc^lcjuhT@byjgIP2K z<{^BA$^~cec9Kn6r{3c);1tF$0RP*D+HE*fPCF}j38w9KQ^nnL%&EX7T#08hOeII| zbZBNy3Vss#wwe0|;}?qsY#3DfKG?=-qLE~Pv9T~9+U4lRSm$)K+>*?vn7GpEN~?=! zmfbDInE7@_%?>}EBIh~QpU>m^>j?V>zx8yR^_A-kggVWa&R)dvKH2733Ul$y)3qxK zTW9TIw}uPMqA&C4_wfD$I5d2f?(7-947b2d!+Lx2(Ffbt9=Osz_oXkf{_*AQ>2qh9 z+1YMy_|M*ge2c7MKh}=kbZc8#-Pab_X=OjN?E9Cv8DQ4Ronq!$hO^DfZfdwW23xe; za$rIC4f^4@joQJa*hyPy2emHCFp9)JehpPK>e!e&W@<$&Tn7_L)e!(Lm{}y(tq#2@ zGPmrkb3uM+NzXDFmqpO*5#kIeEm=A)!pbJ--jyh-Y3L*ezv@~zLevbkd^-YX(*s;l zaS4Iu&@Yo#RmT*64qgKESzVlD9oEU3?g$2FKz(muR6B7@^E&wMERrwJl0Zh;Oo@@6 z>(ti?H<9EE4m5lr(}6p5NV7b=QYIa|IK#>9HnrRWA)uuSv^?Y{9myx(+EDSd1Jd$d zK)<EV(fu z&A4I@37Im;ta#{>aoa67w?|i&c@p;|4)`jQ1M_Xp-Khv}Tj&$RCHj)5nN@cu7ngPD zE~mwh_D+AH{F+aD)RGt}sAQ~6x9a+7E^+i%>HGGvv&epC#V;_q@n<|0yuxg~eXa0{ zyFp*-Qo-!p#JcKIt?Zhq0ATGgCYAsRak;i!wRZ*USZ>Vn)C+q^JaC-M(_to0xLDV` z=!J!>LoQeBRE67`TWl}C?;Y*r@BH8G{!e_Uz3NAQvYn%&jC@d#muv5@x|Jfd_GUXp zZVaH6K%#ACt_xD>G6hH5QQ_5DyL*Fu38dG&^OLv-K!A)+-{$JJCUVP_i;qb`X-B@i4Jz znsufGw%n~l)4U1s;9Pr{g)0)z4u38FchGeic_@K7&~3(`v1C=yT@!j29dl$=*onYI zCh?xm_0EFbH9c++`mVKr-7ei4l`FHjUC-Y2RG*PF2YDI{|ITnNFrc_L-9gvc5C=L3 z_A6{F=6T1&rZzPy~M$QzcXug>K86OU3{SWl`niH^?reE z3|(5Y!7`PV6Hl~#eE4Yp%2GRigxy{?*4p7Ew|Bgd8TzyAEc1mgev$pj_qIFkybC^j zx%9}vcI)xu?ey25YKZX6m4qX=wN2sqA3D~M3!Z}$c-g8?9t_%XhJbe+t7DjhS~-73gQKJp za+c^!5LSpJLoD6AIz$n4;3fnUX4%aKi$b>;Wan_MEL9~8I_C`Pl#k_CY3A!x4(=sK zaieUBXs2=rN+9uE5@C5uF>*yAEavDN=_)7Nv;pD(JvfN3CVPitZjS%)B%q<=5NDaWP#^5YB(uGh227>oX&rZ+HB3KkF=w;@YkJl*iy~Jx6}Z=UJSTQ0iyuX{6IV z0k79B>u#KVl(L?RX(*>dmqy973ACsuwUr?_;8_OZZ@^neNt#Mi+UuBC{SCbd&625> ztbk-$kf%eJrv!X@eH&u#P{JZPbJDgpVtXWkpY^z7VQXQv4P^;)Z<4H;+R-+mSmY3q z7yrP_S@xW{U5V~nK0OKD)a>lWf_n*f`Woa|;RW-tA85DTdoNG= z_Omn9a{K(5^XyTJ=u%%Y6| zaX^m0Qcsav0NrezlI)c(BRb(ZCR4=qsfdzW4=j>c8<~8exVA&|#*SJ3z02(lKm6YI z`Hz3J9lGPQ?dE%5-!5KuNeknE@-cB^Ic1wq>G78vOwI_O!gGSg#f8=WEqCl0#mmW8 zd%LbHYwK*od!+r?`+vUu_OJdQ?H}HLJ7soj_B>Z;b@yq6<+&#t1Aqi>+8eR!1#MV< z5<>Ja{3hFhA)@rW^nIyA7SK!x5Hd(+7NLVolLnu0p3>0H6^hR2XNNq|1(@*AemfX- zdeRubPVGf?x}E_`;i!@RU}C4;(QFqTb1KZFT#@n_f>uHm>@zZVN1Gc|z6&itrDE22 zlgE@>>>rkoZIIM!6CIx_Tk-5(?NgaS;-w^)oUG%R>y+DTxCT*Yo&U@}3v7q!OtfF@ zOrYaj#2MUVAanQ~Gj$nI8ENdyt-o_T!ZkR6&nI4k(b^RrD+BY;R~~9tm{DKGF<)i2 zYm?cgOJ~p0kaV(SrT*2i9hQpFGqYfBeo1yedG5X2@ z7oHP1K=l?xsS~j-SW<=VA^Fl9@MWJd7@6v_DuaCe)xe&Fq~`#jJcSR>A~&&F>Jx0m z9n^+mV_??4g~pk9{FlzA^r-ZdLsa0PO+YGKuDfAE$8=@(A_>)Q25jA2sGs${f_c>z z3TK**+?{CnK~QJaAl0&;(A}f6-24Mr_~Ku33L`C{lO+Ih7d7c(t&XG9-?Pi%14ks) zEGJ&x+Yk8V_ zVN3fb&fe-cGe5NFGKS7g7k#M(Xx&I+GQklSdc%-rmuZWNOXM^{>TU3G%}Lu>*x!Em zr+>cv*8lr|wf7x8(iV>0!}@QCdgHauO~};^Y>VvJLw?e&8xMjm5dWPY)Q(nh)M zQ)Z>VMwcCVvvAKZzKf3ewDFWEQ%S23XRf=freCj8JwH`$V10L5fX8#v`Y2Gzl|o^3OrIMCD%0}j#M3CMuS1?|kHwgsFo zX>QO8^N2gM>%!VW)35V$M}TEs_;mKz0nj05k25;|)W`EpxkW@?$Qlpe;Bf7RW$w`2PmWS5wI2lwpPPPB&~d!#*a z*Owcg6lzC~9cUMyV8;E+_qTgr|Mj+X@JL%da-{8NDb2#-GA|&l^qUu+8n8Red3KCh zXCSgu@ynFnpg7B@G7b@rwGJ|s@1RaLznnsL z&@0~LRxnSv@Ut$A4Aaq#pE^{HAjxcSybh1l17T(nsSglRkFb`M@feSkjNXNxnR%dU z*^srU>QEl1dlA{njxi}|CQCW|6AvKu9gfO2n1;Iv;uLMO#4A0MLEIa+N-xTR3S|$0 z53Oz|sMSqO@h#n}uQsmkkq)_%)33vG2iMcT8`Zo>J!jc+p? zK6ZQi9R0{&oP3HlzR1LYGu_CCBX8TEqg}4ya4)z7PJK{rZqFi5+4!Iu`;xmXF3Ypn zdU2Vjcl-9P@KLsP`k23Fuh}Qtsn4Ekmrk8%PqQP{X*BRV5nBt(?dsZ?xUk<5<|T09&A}FBV12QFH9tSgmb^@! znC`V1ASX_q6A}Z**=d*SIWztg%W>|$?T+@Ax4*r8^!ICwiIr?a4&i7(=8sUQ#ox+ufr*%xz4{G&4DXBWFF*DN?FWDKr*b1?sb&cw zw$Y8Z&&Xqz86GBG+iwO;d5l~WhMeXpQC$~g;yo>zq0oT4hRk!ioW(8mXH$&NUSCVn zJVUozPR|Bk(BFASWafXi{D(@)8ZC3^dhUV(yXcsA0VhHsWQ65p<>>KbclobZ87JsB zA~!71EwI15?eY{93VsNfO)vA|4kmvDs zk`7oKtkcXG6S(R+?7|$-s$SM0V0Rivq>t3joHzhDBVUeq(O$c91w3KlNyqOEfcc*N zT)B_i$&Q@ru*Cp+<>KXR>$sH}dmL$I-<>^v&W!d@A+P4#s{OVF^MU!_L2CEO~SS z{nJ!AkGPdjIm$3*hJTNgK%Gz05--Y?2{i=>KD1yU$I=bBFtk1otMxQtN~Mq)+6bjp zL`z0LdL2{)s}e?ZaxUaLsy7^M5HjH3-IIo-k_xY$t&TLMB;^pj zM=6+IrKA(+Q>lDVi5u$i^~;DLFN3sm(stm*LAL*}Z3?3+)xo?WoCNLdMV%vdflnZ| zxbgQ1nwBhKMG$DvQ_#0B!YMuXt5E!jiX(SC`tC z|HCKp(s_OStR^Vt!CB`e(bHf1N?V^>Y6tJQr7dq=Y};o~wheY_Isf>>ZS(Bc+NF!k zxSwY?sF5eimqLHJuE!s*U#$re^CW@R;cGw-&3COx(m+8cl5{q0wO>3?bmc(VDj*S@1& zxPm^s{2x1{;q~3OQp-ZH4nxnGr0YdXhHj2ofR5kI7MqQDSuW}Xaa_-$PvSvJ4UWEp z-y}cQ5@X`$8_0104 z8FrVIEU+WWJku-=q(W5ZU-l~}9J;BbR#XQ~YBo`?bb{xge;IjvY^&p)4xRWqgPK0) zRh-_dSDeWqpFziVu)yPAX)}g$_TWl!9AMYhaE_D0sOPNKrKc~pbLTG)eXV`Z_utC^G~e!i z#jD$)n{Q>{U1rSwq;1*t#Ait9=cokq4RVkd%A2 zJ)>gm1n|tzgZ)&)Jb-mZYbLo=(YO2*2Ke~bIDUsvmI=KTkib)gI^6-;_5hgRl1aJQ#*u*a*+?HOgw~1 z5(arVi6O8uc_W-y^52{%rs8?5^~>l^DXpO=m-t&;Nh*3 zI=&jiU!8o(-dTG!iG{VJb^>16L^EgToGTKAwLX>pmG8t!tf6h7X|~sv_ZDl+jHo9}*IJI`!?FU2kk z`2N8(?Zk)yeH5hlm{*~)(Hj|D9^^jhlmt&lnIKucx&6pH|4#eEKl+XKU%&hZ*fRGx zHv`&5Y?bd+2C+et^Hl4e$f-+`1W{(Mr%;uiezGTCL%#43eBh6>;1k&B+ifwEp7NRi zbp}(ck?>8=&mi$lF~0o(kvYcVFr$E`8lj@I79Bay|GaySxgJMm14@_~xhK-4PR>-0 zNyd%H4GTPr1uiqVtTA)E$cvA@IA`bSjB^d(bkGbc&N}@3Zu&NOw>K{o?y`c4m{69a+D9B0xrXkTH=GKYKz_f4jcr0vZ>W{((! z9f*oy4Nhqbkd*IL;Qf=Q1Dacfxm3q%cgQf!r80}$04TXBE+dX+$<=` z7+|zbw2Q2g)rey-kEFXRf&RmIS?`&&cnH1z8h%z*>IJv{e}w9cg4L8a=A6kR95A z?t&OMg!$x@q@;;pUL@*guB;T3w2EhdFJdNuR&sU9rAIELrMozELT;`N)_HGOsf4)} zJarR0#Ny^f;%G-y`=~a#6WT>BP626Y3)n#aIpW10k^vVP^9P*r>Qu%d4lN}#lv24- zknbG01~-D1e%9IOUjSTn z1kSn9z%I!Wj~G5#ujj>g9$G~~R{Q~HUEn`5)^$-;Zh1|#ft4yO1G`3h4;~5jdndg~ z-#6FR>9h8*ylW3r!+9w;HdcRCA^+@!@H}zyacKGEV38SoZnVK$=1a!jAjAmn>Oy?~ zt^2SkFE6s&%Q}6<@msm+Kks)QxCzE)_Oh*$!)`B65~+u;-mW8E7t5Vn;4 zo5wzGw`=|K$&_Wb&C|?V?tWGKzPs*kfBNr#zrFQ+|A0vx?3USb>m>XL6a8~l2ThcE z8UP6w-vPqavxeFHDz5_9v_rFKWFBzGDG8_Tl9$7F);}E z@~DoyALED<9VfvTE|G_JsWa_*?rN||3!WWqRd*c*ZXR+`1Cs+b3*UJO%LuzEupQ$v zGwZ=e#tmnrYSbma&(`A|E0^mz;oL4?T;s*k%kA{zPa-D!9)JA=+b`m*^ORu@T09Bo zrLpyP5XbuX8q1?dh8mld^Cg}^HdG6auGP~uem!GwfZ zlM~E=g&4tj=htAlX8oVl*SDMXSZ^+2Ssi+q+lc^zRUoo4h<1m!>i z&f*e=Vx)(1aHUmz#L-5>{(gZmWJK+G@>0rjDvDq@heefJNoV_#rHC>&=%7L0ItOoQ z3tRT{TR+>TVgB0!z^_>jgx%{2mRcoQ#k5AxdPv(37SVr z>FbmiO5|zd?L2YBQrKgWCx>#eeps%SY4jw`{$LoyIy%xph`nn%LJ2D??`m+V-JG-t zJ^2z+qaar|U!&gCh#(Jfd^y&4h1`9|K5LB|kwZ7#)$V!qYum5=%D-qo`%nHmobO}U zPYdY+Re9xI2jtUUwo&!uKl#d_+}Z)k{Gk^rUtQyg1$@|MAiCS5t?yZBZ+Yj>wLksH zZ?*^S|C4sltKQzuUZk|fdPcFWLs7{?nB*+o1Z`#*=env~K+LVWoJh~T{>ge7Sg{6K z(I%`b(z8OO>c7##RpaZ0q;?tQGeWcogPzi59{Depvvw;Tm3XS(sLzws8Qph7x7~Bh z?IkpR1XO_(%}yDwle81y#?=iAJktV`_D!dm8NNv!D<^f6fyU(+cAEA!aS(8V9Ru8@ zr1q_)Bej>s@pA}VyTq468G!b&oWtcNR!#ffC0^>xOH8?T7N}kr%~psw(VKd@u*pym zehfmnTebsZXoa~S>2QXv+CvQ{E(g*1a8_jm)yRNHk~8O<(7VV0>CBsh**df9-Y7U= zD5LP@+=>SdJlsotj>poAY!Rjc4t)MDUAWBoB6#!d@rNE|fZf}kT=+WI47SL>#7mY- zykxrnkY6XAYb%%7mW?GYUw!-}GikT+#lh3<(T{(kEr0g2S-RscGMCP@o;uYtuWP~P|`$Sd~*(u=OFU6U*Y`I%1{0$UQ0 zj{W3f5Jt^H9UZMo>OFZ{U>k^?z%gGbfQh4;bgz}$Khn`ZIsz1(NW*QzA=CPPoJ-K>hw4`nMtsCq^})g&s*ak&+usOWXr++EAHYO_LiY>l-i_Byidg(|dmaZt0=TQgJxD`$swHti1;3 z5)Sn873(WgFb!%T8&D zbh6Gi`C^q$dDe8|bi1D2@lF2HwN5)yIFrA{;O83nEoQ)diORL;c1PYA?wYuDXLCR5Pzc3>b)r=g?7;npRpC(#Tf;OT^y91M=&l>?8JkYkCqsIba!*ye^| z!>N6OTRJ+_WakFpeChP6FU@+BbNOPH#T-3w5Xb!>TUD}Fm7N;C_TYou0JQeRV^8qZ z09`QTfuqOT&9~pt_8&gdj@)!pK9;nfonrRy--q+gN1NCcA~zV64eDpLhxW*gie*HZ zWB^6>IQI73qv`siG>z~3P#%SEzZ{5WC)m;%tvm?slNOnw*Zv~ zuyj1Y+pG#jYr-n}iEmL-=Ylg}Jqt+?z@XBkm09H3vpk0h`S9~^s4r@#B86!nBb+@u z%}YsGw{(n67ccy@U8i7L?1ovUdXAVpf#6rNNMLaG2Bg}ysgW(Pkzfjz%wcouTQ{6G z3|)~sZ{>=Rr1faZjk=QRqlCg@ELWOT6#}q}q?VJ52_Xf*dOF^da)yS40QafS<|bbf z)tTil>CkuSobaI09o0=sCKU$Vlp7?{H^q3-&9*3M`@#yIm79`KU~iDfv)v78J=-^$ z?oSg~0)goI+OHQ0xXG9^RJK-Y~cY-xMf9qpYz z{xj`^ANck5^Z(T^@)gqM^o35f5B%F?XdPeQzKdCQG(Qw9_^5mdcBL%?wVt6y{+~Yk-Lb*pHXp9 zXtt8FJ<~hjh@1Xrogq~z9{D4lUZj>?ZB3`+HF%tp1^aWT9JYUKfzXebAF{Fg24kfg9NPeQtTyBqCUZXrVSr&7;-SN`9 z*p=X>b`vx2M|qKKW&go;;PBD5A4mTx-t#`Ta9mnu*3N~f-isii`!f6FL0RY7O41i- zl`FDa4#X@=2R+{>ASn-_?ZBK>RBIh$Okfmfqa2NNn`mo)($r4#;CVIpIOwOoaI_v6 zga}MzND>1#^W0?TMnGyfHlXm>Tzur=1}GoIf_|3yC__R7p%|>2=80SUBimHm&bK7< zDBU zq4rf>kE$Q=v#<%u>0aedh4WfZ$vt=}vUMNeb(Xz3F= z#zgDdJYZuV`C$kDl-Ws(ZTkXgiEo-GFtvr-EauUJl%QkZnQ|fS&A)5$m3Nz`aBQGU zpL4X`_m=mxfAd?v)_&o(+xRxboxI~pCq$p)lO<))*2PdRl2yLaDG3~G*ID`kye28g zf~_*T+AiYszx3Yw+Q~;AX@CCF54JbG`=^;yp^pGp-3*ZS7=3f|;N^r`F21G1gq{YS z39~X*^3MDj&>h^L9cpK8U3nbV81-?M`in2#Yr#}6jGVgI)wc#%oI7jC3)6Gg9P@}p z`_#Fh)^@3EC|2s}b!zbollcbr-!cohR(gq@1RVSvL=$WrIzDvTFMsJv+S`BlhuX_t zc6a;l|A+sD?ExK(=XgoeIaLQD>!lwYVs?nSsPpV#l365ZxMPZ)iGxf&YfUF?!>b(| z92nB{$fs&MtU0432BVzoHg)us#lcqR(oZ!nurB-ZrOR!Rz1Q8wah+LtJ9lSVZ4Wim z?YeZf+~Te_kCh#92zmVN-~erByIs4UnJb-=v?bvjUi~o}1fE^H15}(h;+o0XUwq{j{Q@fBx+-= zVO;^lV5g~lP|XPcMnWLx*o;h{vWg$O6=!V(hiVdn9|6DPPi zS#L-0yc^lwicL9+W;j~zP%_Q(3%yW*PbvoK!1xz=g$k_f#k72*f(ePJOMxJ#h2G^4 zZQ>=lz+8wc3w{-mQ<4i!ke+AyWcd~GICFtAXZ(of%KC5J^h$XoLM(NaWXlg5DoZ$e zjV^hi)AEi`-gpPgL&AWs`rGMSuHY00z)IlYYO}lqXnDE5g1n@`%b2otCZQj?fO->F zc10Ft224D262ec()kCth7a&5>10m_0Q6j zD{ecDlSh*FGu^r#dr7mGt|$?g)Ad4c5k1Q*!0B6{nErI=;)H{7>6g=4aS?}z;ROZd zy23XY+}Wz+=`=~t4PkUta3aW@{tMdaJGF&G6Qm_v{_DoUz7I(>gM7rXEW81(nScAN zx;e04*RGaL^Z_Z?oa2iCm#^4DYk?b^vsYMee#_nMT|e>D?TT(2FY(VAVO?gHkrZJn9ngp3}98M%KI9`kRmsky^VDE z!YX*}_+k&Xve>lO|zV2=u?0C$#(x=eXjlg?7eA_rdM_6d2`?QrLr<>FO_x~ zBmn|}K^U`RY!(~ifpAB(+dUBzGcg|~W+EnLBKpgGm=E)*CptQ&+dVyQw--zs=mx}U z5Q_jU2<;`6O0{QY<-WhUy_xg-o%=kQStWs5kP((}J(=(OKF@vba_+g?|DJp9x!oMZ z!6;Aw&!UfmFLu&h+6D*cE-rFv9S7Pt*oC=3I`>!>wNnKkxhcxYfK5`M740xOe>>~k z`Pwjb53o)AP8gJ&X=Q8$>2YTpyZ_P4_Vm#uB>Dyv1jhhCIL(pyY-4bfXPjtiJcQRV zHfj-O1GSi#fIypR#DE#G5G4e};E;p(ZFG>vfC_Llt`(%%0Tzsx^i*>Uz~mB_03x21 z9z%=D`NZI0Xx;<3<5q{HTxoC&TBKP;5;V0m?ob(YIuI*aZv@z@g3--^M@~pO0O1{n zM}nX2oh;+7%%z2?E9v~1^XcO0=hI^k{Sd1%9UOqtl=}MnI6ZGasxus*G<`W$m*-L& zn^>Eg0xFOK5QDj8A^b9r5Wy!njf*+qWn|Rs$wMEDp2&On zoO3KQ48fnj+hY#l6*Us398j~b?5q_X&C;EI_9^x(G_x{NO+&|T4qfD&aPiHmiBh>- zXvScwmILvSUL+`J*+qkHk(O@p!5e<0#yl7-t{6C&$l(xtpzw>@763RT^ZK{1Yvc z)7=5%bwQ_z$v2bCJdjjDQULz^pacKbNQkgem?LiqTM)bJm)nRPenDG+J7YJBNXKyc zFapxVj}s5;e^8-6?);Q-=Q6W?zz5oC4jU93OI*fnD5oe{t;P3Vrj0xNE?sg0hGb<> z@Jl&qe;Z07Zu6EiXnMw>&m5a~@ea3`#DrhpWhVO=lM(WzOO_PtSK9I?S0{Y(FKs#R zIeydeYz8O!CLVc@dPScReMb(XZ>d!>?`C<^RM?Pd&v8Y5EiZU$9}P*t2Sb{8-uwwS zd7sM@=O|O+goagVz+a9nhl&0RnvE~>i*lw+8aENh&@V2oEwdr>o-~et;Zf|Hzx4yZ zpE%!?x(PoZG4f|Ja3k^^k>@mGQoY?s!V{_I`sou<#8?uvk}m}=Zk~VFhd-Xa^w*pU z`unK)?LUUjyDNgZEjn&kV@<}7(MMI|{+GF;xDB`+2`Ncfaru7xiie#x629-Da0j#yA@remkJaTyJ+dL)!=VtLm=i7|bHo8#pPYg5o zqY^5a_5?iDAOunejvP!EE{^if!9sw^<~7ti?2sCe+%B(H()8RSI_nK#EycqmOv}2f zq};AFBr-l14OTi4t^;V%J!Vj-u++N%ZQj=*H4p18Qb#kl zgcEG2Yc42qk`N#QS{)#iZ-o_z2wV(Kd7b5!Q&@V_ds9j z!8(kR_JN_{)VQ%2)^XaJYZBT^oD{10O85;Q)q8iCmYDuhv;Y7=07*naR2~7U0LT>* zhWl_czXGD@p>U6hO;HBq06#g51HWL{ z{zzlndpQd*ZJU1vR++L$A=6H5+inn>!6}i*tVljn9CT5RMkbF6QuP>ah381CBq6Ub zCQZ|T%xYyQC{jAWlx77 zC!C)9(G08`Cyh*B8QA735`hdyWd7-G6XAyaO0=~IXPM>|EQu(~Lf#9Awc?anlBv^A zw1w@1?mZ$U>07_Ee#ZBH897%8%;O=u8382fT@&i!N_GsaqZmc;8V_T z_%VI_7B0LBP8EJkXZuazhww7LtVi5Oc|{_-Q{vvByPb&3yv3@}_KPz9(xcC%8@I3b zE3!o`E6c;*BCB%Fzio#-$m<>ga|%EDrm{eCQ<2!*G43hddyw_(QM_I_AmGlr)@zvl zt6&PyP7M&OE~6c&b&QW4wWnG-SXdW5m`J3YRUK;3Kc5SKg4U_=@zjLM#_on@9CkJW z*TW-2>D})*ihlWKdhGGXLl3b9Y5Y3+h#@HgG=!=G?^lqDOOui{CAg;PK}Q*Y(_|gg z0bUV5jhRPCmVRJl^j(6O++Ngqbzq4xjCTTApBpiy=!9Y26Gq`jcQ`d{4#f280QnB2p)(JYcDfUW6CRxPhj(d)MomMip5vi0>qzgN)G*qR zKQ`N<-zm^Fe@e>gpwWqf?Y&+r4bm?!7{C`RvuSI5Ca{)=J6+(^)A7rrL63)>w6-** zLx&E+zYR_`eLU5&d1`H9G`(=<*;J{lh7I;Z$BsdJN_}iL?O?O1`m95p-Knt^Q;z7t zS6K~FA{+B2_`jk0P(ur#!@N)~QB(YeD-MXzY*COQoXYaipY=&!;f%KoM6y~7^@$gn zBy@7LN%=S-s50QYLDeIhYLIYdYbu6whA2m)F!DX&Ou-T_;45)CkY`{9LyNTKrGALm ze?Rcw*ust9h?j5cNTdV*z4RNtxN%Y^e$i3gR>=|MM3{)pccfkXMaaUR_Yqk{pJ^$3 z_MC=pex#&$!wdg{l1xs#F@=aO{Nf`&#&38F2k&@Myfaqg3Q&eys67-f^SB~((lcdD zIa+@+xFjCyZSc=juf8IB>9hg_2s4|6@kNlp4dt41q{G!$gfE<=&&zLVihF)`=g;k4<$`B zMIMNgMzlzqauVjo`LVfKz~g$lfEm>l)XvsbzoV}SkO`qVO_R%IVf@mvva(7ahJJRW z0UHBol9xy}w= zUh}i>ELZC-wL*(mTeV29b)${0ZLV^_T6=5;{lZ`T-)V5C@>k2#9s=`Rv>E8+0=MG& z3CZH_9PSxR zV{;Q7JL43|=FXXZnogAP3^J`PjcNa2Pr5Qalcmk1QC0L(Sg9cc*Qbvq^NIVr_+`li4#3yW(!;6TXuR10{2#I0<(iMb>#i-aUpqZ}7_R1>*YJLg!rx zyed!e2{$GL0VCuao8JKAa>8$N9EXr-kGdAUXThHUHHJ$hc;0zhVT|s?k`E%WviZsU z!RUJ$yE_lAAHZf*A`|3-FR%j?C`5vN)sque^K@^l1is+ zh!zoL?155|Cw@2hNW1dvTmtCp&rO!{MO*#yNqT~@@l9OP8yvBwWt9belzz-< zNaJ}&a)jrZAenCN;eSny`z11OGbQ0s&(NCF%wQ_3$K;yt5N9~cmH3=h1#b;YE~pE& z(H5luH=M+dH2}WYq$@q9jl1e3#%~P4D+mp67BNY$Z=CE|_O>ZLmaz?Odx92Rg8xJW z??bi8H1ra|{USU>7txukaO9YsyUe@%Asvdn4xUAwG9T7U3PlOFhda2Dc7?vawSRxQ_q`uY-~If5NFV&f zXHbu6BR=8cVVVaJL9db?P20?o)a&Xng|M&<|s*TMyhN7w5e2`j#BBjokpX_ zTGCN>0`P{AmM<(Vq|+BJ0{q+Q)Eyw`TmigRVwaZ%Z7zup^be+^M~e%RXG9_ z6ZN5A8HlE3QbC=RNL7=s;vJ=+4ia5;xQ9P64C}h zRxKt#5^P!n$e1Uk^BYVCq&AbB0Rlx4(rWqbAW~2ggO3t#j}(_CH)A>IIbdnc+X>7n z{LzYyr<7jiu%rei9-5MrEH|OLa?sJ%mU`Jp+Nf@NUw=$OM$ey36F=pUCE{!EY)_l0 z5p{KTr8AdC(?j3?cKVGw-<;aIddLUmvD%M%QWIdLCUp{jBNE9KcFMXs;;DuBfM#n2 zAcBMgPzy|}0HjS+fvAseGE`b8C=ej2;^W|4C!ismFec*Qy(hxPNt7^GZpet>8F!iY z<}P3Ml9s$cv#bFDh72n&i@KP@8OE^r-S$8TEoB0x)Tq5?@>a`lenH+ME*}jWKSf;l zA=8$R?IB-9d_LgB)2=W+=k(*wAb5}jc^4oeg5yVi<|x8btR#gW!6!T;qht?~(4 zrjEhC$eXFrFX)fx{fT_ZZ0jy%0ui) zi5M(D>9t)LE7b#2V}6ov`jKC8!>{$7TI2Fz zj*K5}^nkl7X1N@p3O9kiX~r|DZ*YI*x7 z{%O3=n}G34C9oWV&*aN61sQgQ>=Dg-2+WHH z6->86NTCGpl#fmp&Q;~5x9it+4*XmWxB;jx?H}Hk<^apR`v#*!9vmJ{!yF-h;rv*- z>&`pUJ@?+5o_X@=bUK|)FTC(#fXCr|y`eX`Oy}F6ngu|%v$qF{>{4n4++V>w;K;~u z8X%r$o_QK5w8@V7<frwf=tFXmX`sO=&WD7bRnI3{uF?@ z3(&7=LN0qn#}XQW;J~nMI-Leg-+Ry90NRFh|KEQb7=&hERazZs2Q?0sfM_QZ(pFrm2C4rocvkPe8L%9O zF(FBFaMhNvG$>tnx!P81w&KIRlL3LbHppW#K)jI!ed0;5ou$n3Fu#k;LR)NhP#Bf*LQ{BujnI^%trq_K+z`gWIH7$CsEcz`&F;ZjkpP3=^lNK48)5EF5QK``J>B(}pm+(F~5x4d9-|+Y7 zv2@Sd-<2N!-q+JRe(zIh+(_|l4;^LaV}$d!xCn2(j8}z|bVUArE@&%WZU1E2OApyO z@<9N@MqYF_V&m}IeV<)k^|w5aNpnG0!G%{H&p&j4%o6*}-JF+2JQq%p*C@HYqjnF0 zd0DPGE0K>PGtn7Y>ePtH(B9F1J*IyI9H^q&@h|?xzfHH^KA3*;#B=G+TW>}>f=U7= z_V%+m@Y3id)KmI^}UgAQ`HbjQ(j#f?}5RzEl<2*#u;4e`Yw zs1&IH5YlL*s@=qR;j749q#iXSHl8{t`-XPu8}lTH)ZC$YtDrNgx(u0Suw`H|Z9J|C z(oLCv^Wq?Bc;gAFCliG%xy;@isPe=>`491sAAy;JRXu>)a&SXcKB-U%JifN2v#MjD zq5};|^+O6Lp|~lsW0)^&A`Vqx`k8#S zb+o4@CQnuL^iOl5=?tnfXP@|4s%>kHJp!$)jvPF4EH$@uV)n5)4WP@e?e%t4q*gfG zbXh*M0ND{Qs;4hMrGf5-B5*aZxm6F1FzaFR@P!eMy9 z&Gt}Eyuo;rAOYS+bTKgF=J0LW+(a1e;xWt*h9fWh<#Gvq9RMD5k-mVup2S=rQj%}# zMrC@|8UMVO|8eg-t|GfZzj-b%{p6IOUQZET|J>W(^P64GUDS*Ss+)DhD}u#c{wjjv zp5N;laR-g18+C^8u8#W~vUuLd^wTu0a}nM*MIIx1>mfJR?}%5>TgK^&OixfPeUTuzjTY5VEa-niR-F7MS9_E(~3GReOXnaOgsP5B}8C(BMs);Hs-`W^VjtB zVtDWPKx5RA;55$>)-v=J?G?Xn#;#&M7DSq6k%+IYucmd{`^wltsvl@d-PH4LHeWX? zT%f&e5ZKi$!KFEpl{f00)bi1L<~c=yZN8n^iKQkWBJHW4=tz9yW1dWpLTLQFhY8#~f{AN_;$ zU;fSil?D%;NUg_y11nPW`!qnyq+BsEE%O&N6N_!Z-mu=b71SWGAwVQY#Hs;B4TKjS zkf)kVh5qj5H{YAS{+G|B(Z_$34&43jbY;QK0%FGMC?aqpA z*`~W!*Pa1$+Cfy(-IYTkecpEaIhoq4%v_J>*OLRt?$XGCUQVNIN}fsc+!N1Yo^Cxr zS1)?zPrrC3J@fPv>6Tk=O#o#;n!g?G9PWcfl^KqYSDL-L#wH&m+k*o`=rB)E7dO-U z-~Yk%?QeV|HtRLB>F^Sg^S9pphL8Y{T^Uc)({q5A#x%FM3_xB-y#?UPAQ(asdd+%J z&B+%|hcvm7L2PYfM$DXnG!4|Y5K?eEB|Z)kZKz-LBjHs&!wyqDXa`m$U`mTc$pX!g zJ|m9mY-$f_zfxT#m$o50g=j+Dhj~Nd36MsfRKpNp*nK!iI+*3nSkOhhxeex?F}KOh z*ihSoUa3;yip$r4T`wh9_0V8?s;Wr8SELw>XzJ+1-Z~SnE>v3D$afvmb9K_)v^p_mAcVuUtU^D zZA_jdy$utNgNF~MZdPd;H_^>)>jE&ZVS9ZowTZh?#34&*gLGC=w{d@hY|0xz&6nM* zWtF?`C`Viz5F@YVI(TI6tsO-<#}g6z-cRs8_ov3Tb8!!|<2CN(8{-j#T6%TV0d`w( z)-82&VK!DlT6q=<=-=c8Ai1b`;D+q@CXz0`NJIL<)t5OBf|r6kb+au+C6+;B#1UTO z%g3xf<)dZAw}H8d-|(dyFUm_lWJFpGU-EC8ifz9+u}q6`LRZdR5mOGIA9DQCS&mOR zZr+N#B3u!SSp3s1|Ca9MFYYp$gmWG;PgTtkxRvCUVQr7ncy$^%uF{Xx8D~sll$Ceu^jeITvQOtBvWUfXAwE`Qq-0t+h&jQT_d+zS)tWXWZjpUiKcr4s+~8$Xf3 zqP7*_T_-0H_BFIv`ycZd`Q$6|l=~k0vf!-^jX3%ChhJ9{;j+RBuRzW6fLro#?WMbY zNJL)5g9GX$q>T#Xo$vmg^u4crF@5Nd22ySNApKvi&n6i41cv5(nH2Guj_KpUelGet zqvu)wVR6Y2ac^nRuR)jk@$bQe;^+SLzf8Z|w?B0syfMupya_<&Y|~3#o4~HCtV_F` zBCkGg?y9%BwcuzJ`KyS@_;55L-^F|VTz#1})6V4;w^#Y~eUO*EUG+|_ zKd(`T+@>XNhgs2wm5*f0oV~QR{qh;wGhnV7fL(EUZ5JADxsZ8dM)XooS32hV7aVwUa28a530Lo|5kDoZ14l)Qm{K!w^(3)Dnr4m|!d6Ckk`@U@9aGl8b`ZfQ?s_k(6_>{+ z0o7OupmTOWT0zZ53nqd`k&px4I;taU0MZ6%*OvDNelk#DFtlOu@sOk#FiBS{GB&E% zbR_5{4hIXn)hrFn4R5=3(kVMoRIv0=S;n#gw5S?m+ssWf4{*%izmM(<-Q`WLmaj5+xvU0-@w624Vo6)(W%$QAa|%(;^qd%*R2S z^i(6sDmods3mgq`lBEU63Z4Wfz-;etn&2aZaKrDqu_;l#u&e9ltHdBBT@B(0)n z+t)h~z~`u58&i?6yN{u-dx%ws#?;u= znufdD((!}C9KQ`Ecwo!q&+H8l#FGI zrx;XoN%`%EVEp>t*SXIPcT!P8@5Hj1JqojIo_*{`KT7=`3cvQglXFD~w5Q(h6D&jECN757dGcMokv zIK#%}vmnk$a~Shn0{Ea0FH#je^d)KAo~i;|=nK|GX#K%!Kh%?`N)n+5-UxW()(WXJ z@AMaQNABfGj>}jIKgLCxK_5;TtVQtbhi?O!jA4v(M^dkwSB`7@wumq6$H(f1W>n!e zbd~ML6dZRmA8bSLkUputu|3^!&pqia@BA(5=n|W%+fsLLKY6Q5&9u)MRtV)y_r9St zkBZ&a!I?D6>f*`Ie>Jr;VGUqz8AF5q;!oPmfFE649a7CL`htRnU>xtsikEc~YKw5$ zExvOUk(W40nKA;d=BVX6&!wt`JhiSwJ^`)9Ckck*1y6kVHCV~x;7>SYLZqK)iiwU@ z!+J}|=rfqGvUzm{U_5Z_=5!Zz_Xl73TzdDX{xmJI;$>Oo_FycgW}JNB4gcI(Z_SYO z+b0>b&s_2&NFaUUwH~>Wvx>l_yJsl9|I?pI|M_qJzx2^R{%=!D?|uXnIoQj68&whO z)vqJ|U>Rxc`0&nG@p?Nf)CWzvW*@nH3@@Lrjym#PI_*RqE>R72fOM4culDl>l>>8WtV3$01!*G9%j=C(9jH^K(UjJf4u{}X$-T7?YtYi zjCn*4z+uDE80sXwy?tTw@2RJrOdZ(Qp2O_m@V;EdiuLjxz__E`EQVSy(++KWP zi6zWasUPWUEG;up7(jA0J3Al1+HF6Ig_ZKOA))WW(#Qo=a9WrsxKUB*p8(uJ(rc3e zF?6*7o=V&eANZ;m#PlygdbdReuqB*>h@i9t2L2jefF+CI7@sN|u|hz$YnV_t_NYT&hlw=v&iEU22zpzYSjm%s0!(@q zXEYs`(ijI%1x>`cHo281pr2tmRy!K0OYJ?~NZZw0-{63x^TfBA&YZbG{R2|0&8Pjx zhSOlrV49y~(=npy{YQ?ajpf-?PrV7rIunL^CRCch^MIBN9Ccr32YUHQmbg@pv7B|wp8({>6Y-lyjx*>qQpzu0L8mHJ z0tAcI%=y}SRTI3+?*vs&4vo+HtRLG{P#M26hkssVF61W~qR1T3EA zu~(Fb5ArIm$N+c2mF(THwmnkJ@$fq0Ga>Vlm z4OSR864Uc^(_7!1&VBQ{oZyS#fJwG}sk1KAwoJV;mviENt5+WGndJpt@}<0TJ~9sp z5qZYVtn(4j1gPORa#rxf2;#ZmkCXa3KzY4=+b&B^4n8&PM9asw?3L;m8C~&{Ry&$;A(63JZ^%#EbIY7M`MvdSNO8^)K zk|~aOUtFxD%Yas7wdwfXCji_4!PONc%p6j~K{Y6#BMn|kV;3v5sTvNGKnEGAY$GZY zs{r$*#np7-$_pV8ou6M0iS=bHzFa(WCiIwRW@e(k{un9gO(%|`9@C1MLR3Dm^f5QL z5btid^*7SFbLZ0?cixrWgZ{D>Tb_RMiS*M4A4-Rg97wYO_<{Ys=^oyVUb>vdCZc`owa8rOM68jpaqhQYTS@NJXojB9bkrs%4J%`t{g+(L>IoeJR@V5>;` z*Bz7@ZH&VKMU1kEa&-p6#^ZYj5Qc2xQ@Ssmf^Q{sL~GAXSf!Q>r?|()PMi(n61{*d zcjP<+z6>Pj4~q!UtHjJrq>fF-W4EEj7Q;xMAXjO>ISJ{R_juBum*b@LD3w=l7HW8> zq--5s`dZXkunq$wP{EG;G&HCv#RXZ7fPlO)mSK{UPM49W&%@`brU@o~ZNyQf3_4OL z)?wCox46uti4};;vuD%A3!~}ObI+uG`-d<~w}kpYJ0=@%O}&^%>mNFZ3IH@Rd1#TJ zE$Ez97Sk$qx)I&>2J*Cq2{-||7G5?jM>6E#F8!K}Q~E7`!z?4eInVJHKe4zUnFuL% z++~i0G}VZqw}nRi06mAq4FAFK;G>f`o0H*5$k`1vC}or$_3C{Sb;F|M z80%YU zZ4vdL${Z7OtjwT#(Nwd>iU`pY-uN|tsr_<6jV2M-v6m;0)ccPF)%`BB0gYzK4Y#hs zL>0o5lFq*z?ovCGI&Q&6%4t8fW`k>uC3J%y>SMd^v*{&`54J5wJgsEe~ z+S=O1sly{_2JqQN-QA-8Z^DNv>Mhm}Gi1|q-vL3RO)V^L(QnIsgcCVz4YjlbS*o}+7j{GSQH-k7!xb*T_v*)$*;uT z5f32Bxxs;V-mhDCX!EtG)CJwRTgTu|(4sHniv)<%s7yvY5`q986`4di1pV@r_oUUx zF9sho51lN*-*x)WRVvQA-v4{)OMmfi)8Nm(pN8*vd%7HSQik~R4+TFa?ZiXHi26=k zK})1hde9&>q2dIaL%;wpOMtKbKFML8ely+n=69us7;DZy^H4f`=Y46S!p2i3%hpKS zxb)jcIr$9YO@j-ke3^D;S?O}sZ}23(a)|A4JHfB=|4Thw6RFABPJ&8)>>G07{8F+1 zg9Wm^#8`J#{(Rt5xJ8??(v=Q-K&|b3?=EQDroW)wva#L7b?xuorF=c6UsDdKj_}$4 z>3>gu29k3Csa5&CeoXrx%t8+KETnR~bZ=@J@3grUxJ? z%|3keNSd6Q3Sh0;%NqHqQ*V9{OEq(I>Fnr4>S*3%qoSKtYt!<=BKc6A!vUY=X5z2+ zRf3|YR9fWWMWM$D_sBMv9TFo4J$~qZs+xf7COpf-Z`rP)-G4yD2B&ha zhfcbKwEhVJJ?f8#^eTD>-SVItgFGEX07Gb1lD!46P#s1ZRTa_R@(NNc0h9n=erSQk zFuCI=DUg*CRi8tz)L;JMy>vi>pweNVhC1FVSD$A;tQA9^?s z(hTWKsQI)Yy?k!u09Io9(h9(QhE2$CdB=Ow(Hm||YpB001IXG~g@HoKL(VfylYR|( zt&3Snuo#{IsAEuIpy$27uEAdh_H1`suqhzWKYZXOau1Mlg`}3lqAZ7MEWsOw*gzGv z8iJ-A1Xyx2f2a>oq+NT@0dTa936uO6pb%$WgmXg2hj_Hn?%^(~7u7HsY6OjFpxEbFi*C+*zkrv99AL>=oKpV`~ShM6F16TiCSQ z34ZR5Q#s@3)vV z@XacXK-NvkD^V{zNUD}2-tkTyQN;wf)<`;>{F>_4QYRA!7##08f{e*nCCxH9-^3hf zcl&-Oa?5F*iFQK^ho-So(*~FZsuDL75B97yS6iV40R#P39eiAe-valr(n49rW#V4^ zP+KX3Iwt9p>5U(J18P;>X`Q_e9tfsdkzmq^m$tp@BcHT$e&vJFDWTtF!qdP8&~?GQ zoQn7@*PMnRJ?fwda_@x3enf7Dr{X7b*#|!S`x||cK7HoPR}rmWq~%J9afko%o&Km( zhON@CgtzN38Z_u;zasdho^Y@4HQHA* zR=Ga#$^Rz(tN-(VOP~3ZR)jMn^vl){`X1U$BXLIZ#trYvzDPdApZvjpA*@NM7slS5vBFZwESN6T*!!WirIOA7bai4ErH{WtBCRarcb_lVj6W+dqVoT&*0zom5Yv4_+5zWbw)O52w$tj?u{3Di&kiHp;#sb#1m z2GcIU;Mn-(P=6UjKV4IV_NP^r8MOd%co4}ElD_^P^{6*dW#|G7&!&I%??0ao^>c(g z{?0uAV!HX*p>Ur;s=A0ei~8QD&YTA@w4{AZ5QclY(v`8x9Ls!XY${x0fL6lX!URA` z(ZJBY02y-}x+B;YT-i<-XX3%#gIgXmVbbt z$S>f-eG2l;G`I^XauaAx?^={GXG_zIX?|)R5QM!W9N-RmN47F!{)PV!;nF5SNlb>(C;A zMJS#D5cP@7`cYDBe&u1*QsQ;;0*fd+*0G3Si?XbeU?-CEMow8=r;gQNn{TBu&&pFJ zEuznS;l)#Ft{qdCOiY$$rC*so>vp^>mvKzcL4b8UVi zEuk`{1*66$CffLJfLBcb_sYa2>OHGR1FcM=P+ekTsxG}!Uje34b)}k3teP|+v2Mit zY1rkqe4!mkpK7q)<0fENpxU?CguJnkI=7anCv29U&l6`=*TyEUumaYW7UrhX5|i~- zRIQd*E=QYcL3L<;Zj%0FF)cF@p8+hdT{)2EXO~#*Vgfxk!V2Den%crLO3wfi{&oDL z-gc#({Vi>*5OO;15&)YCf^}KInd@{pzS`25IPh_=Mc<*}bo-m{0c5w5M^8GYT*=d> znJwCl6E^FDClJ{7U`Oy<33_w?K&nICuHr=1wrg2f2l%t@c!i7AfOpkGZO^tH6L~!t zA8F(|=r@xCfpQ$rlVix=#%XqnxUH+)3PNpVC2g_)rRXcHakfeS^c?8~Ejcf_FCw&j zDlYS9pB5`V_I)}~zA+h~hI0o$Xw`yb{%l7}^a0J(rT2d1Bk2cU{oC}m4}XTb)lTYE zaNgHDX^l9|w`H*F@&TH{{<`-;Z+445P+-oVYg^Jl*Na7*!Sucl{9gJ`fBRSIpZv*x zNB^hV5i2C{c%AT0IxIIL<3se(Z^dnVnWcO<*^b-Ky~A&$Uv!kzjGuc2tI9MB|D|VZ zgP&KtOiz#T^@_eDcXWNeA9| zU%K(86AW-oX#wff55N5_fZa+uI5M0jkhV{ww|?Q=*)YL4ge~mL^D8M`oJmbsfVuJT za7fhWm2M)*ZUls>Mo}ev2U5DXoj4q36Sp|6qe=(*_#>wPO@N}7hS*eD4jx-bpUwf+ z8vuUeGjr+7U;BFM0o*TFDjb+}E-fQD8yvt&0t3kd4?UVb^6qzXXiiUh=%w1 z7^)_icZXK}xN+6-D_byN;N{N_T0m%r5Iuw)Hvj9Pmkyt?C9e9(5_zctdh__F)GUui zr7h=}VB`@A{*?nRX#-aCSRx>a6pFMMR^gF(SjTjn{D}9Y?<7K$9UG(U$mAYzI|&i& z$00O=1HwAUx1#r73*QB&n)5WhSaLTWGb2jnXK_Jwszf=BqT+Iix*N=ZH=HC02<_>_ zS_WwhV0Yb>jqxiJX$o~GdHDDbf55><>!}B+dgneQX>2}qK&{`Zr1suHHk`Hrf>~uC zE#%l~kqL~}bj-d0R9oyEbPBzK2m!fLQ%eRn^bC~9GCd%lZQ}>+sIV6UkiSg2GndlB=y?vwxtx~gE~c8+K2|6?S;^{&RT%fCbg&P_NyxOTL9zFoq7r$lTR!(t+OG!X5}Iq_g2#E%mhH6I*Qua zG!w9H;$EeVg~~#8B^_o3&AKx?H^bhS7HsNwrfF%SjOWKUS*_U!8rJ6~m|+3>RlPw4 zuaW*^ZSrE8Mjfn;I2TZz>!lB9qJ7%-Z9kTu?KgmM6ejf+wIPa``qfN6T-hT^+RGMY z<^gTC=yJAWsn@u222d81#e|di+{4m2urIYx-)fi~G@2&mV0mcp94}GNiIA|YzR}OP z`IDDog5zKftjKgHKHQy`@~d^bwgP!Jv=Fuw_5l)8U)mlvddQ{`mL5 zm_G37|2{2kwWbxq$ZRF;_6N}hh%NGD_#AWe4c_B{ca$7-NrplQ0l5=}Xm6yoz-rLp zBR8gZy!Gwr8(;kE^np+P36rACKPLgIUAb4p@LoABxv=}X_IdZam)^^>-4|pM?~aSN zmRfn4X3lGoz5!n0iZJ?#c>UUyz$=7(Exa~|@v+Rg$}+Z0<2k;4QQqv$@16m32hFUn zNAFM;Sf+iO(kS$ByXM8VkLx$rIq*^r*g31qC&p@LfVD#vd|96aIDLKl7)-M~#^w51 z{I#_7q`_k+Qaw^Ak74d(qvn0T+ZPh6qsNZLvUF@9MA~-p$)BY~BzDJ+9AsH}0sY{U z>C5+jBMq?0w3m3!o*zxmJ@^9RwZ`=J-@1)u^u|!JSYioYz4nIICN_9>=LQV;aX?h^ z&d9(rJs^ZZq7i`6fvw;LbcyE{mLoq44CJc~3~#$ZRGKe;KCoY25#f62K;Hau1$XI9du4$94TxJf^=9PB>4F_9a zWRT811E_u-OYPd+s~`=bY*wn)FxU^EY7F&-DkeXx<`+5;fd&XreL?8sYS1R&Ond4cZ(a}Z4T&76q!rM+ zT1!1zcrnescrJ}zIE$K8JCobFv@+RliLjK9+ScG8c~noIvZ20LnP`p!zN;iR$EM~r zEdFewgC7=$NPPnUYTXc2JJDTVZRHo0F)~#T&Fc7DzgFO-6DXCh*4TF;D7Hw}0rfS4 zK_&+ENV%0FudKL-U@Z;|TcNJEpgPt<-JB-;Iskih>T=q+!l|%=U(#r#KCDvDn^}Rd zFvm_lN1dasRwroQEu1L)G<80uUfPd*S_F&>j+@vl=^FheQ(&FripwlF@*) z8!MV@40mM!+5yI+<4CIof#hNm^}3q#Q&?r{OsxnBw1(8qN{ahpR?u0hV>O@|b*NPi z54(77I`+eKHFv~Bxk=q?%47urfd=%olq4@=)rwy@&jf)orJP+6VB1NmE?rFR(ArHo zv@%(#P>&YiP4t`aKUz@$)U1Z0`jRUX)_Es*TeN2(+G>S4eyiBwtMSWT=+3|={4~UCQA*f#!)wHjhl6B zy(Nzej1{-P<((KF7)#GS@`H58y}zAis4LbYX;G5zcfN|aizk0Md4$Mp8R{Hg~%+HP+F5B4(?&nv!MMUMV4h2uSvn-822hOqb@S4BmQRFi6{Bm^1 zi>R-AnVCHU=0Ox2iprvl-y$Em@|F+2dASVkWwx%z_3Oj|G>#dFax?cnH*o3@Zb~>* z2XSy}q|*y&2yW^F_F?ubHvVlez@uNjUf+^xI{MPMV_cZQYCw|Ko^H7F-cW}*eC$ZX zdE48Nw7==UBd;OW*$5*V2`wT)Um8Nc{@af}nSS%FwV`L;!eBpqU<5OO8I*qN)1M-R+R#%M zSU>gn%Omo7m(sQ9m9cDo_X=z>R030{AHe$_( zi71o1amw*@1@niG{4gzGMW`Jx*@?aW9#p3e4v(b$hmNIIbXKcOcADT(D{3XpNbH3% zO@eV~ac>1iI8a+y#vTNDB44YhTa2GMl_qA!Qb!vge`6_*ZLeKN<%D$HI4eKruT1mH z3AyH$$y#eICg8g3QV;tp<`+6x*|~&zMq8Q(TraZ8tp>2ERV3A|0KSPjq|SMT)rax<84eB`qOMXmS#7d?DcMyWMD?M*G(>%? zh8jsl2{)@nbPfP&?$O<^vXRu)q?Or4z;GiQc)Mf5F*i;4E2jfYwrbr8{#YN?2VQ4& zp#eV4G2yMDO}0W$Lmhs|`4+r!(n1xp8V5jz)HFB6rfpEN{n9M=9Za^A?z<|`&8f4i zt4u&DuG(O|h`jbQ$$8<0Q*l_9<=oZRo3?0&E`K;t&*-~LGY;(z>iX^1|(_uz>%&*W37K;~N++kf3}#$LPp20=SimG3jz1@m@! z50`E4+*t7Ds&p%ZZ2$Tq`*EBOKJ%zcE`wc>zaDPOd=|9kdb68wb7oHc_I_`!y#?mb zRT5s*&!95gsI=v~J*0ncxNtp|*E9!|<|&1w`=SnIQp+wctUTao2bp#LlnBwC;uf3c z7!2bA*}Ej&1XzsY=5Z8MtpgNPmuT(m1FT{VgHE;?(9zb%QZ)5>sg@Huds>jnbGQs( zs%`jKT4f3T*wG^ZueEgQ`KQv)e)?Fb%&Y(uW+x`n0@A{>=zpJj{!BNA4#LAPW&d4_j6~@rgP^nA_c~x3SdA`tHdiNHt@5RG#3C^N*i4b@KByL0ACls zs&iBT_W}nu)Q)iCs$JBDi|YBZNsoNzjil;1;|D3DptT-}qeGfyDscB86xCS-PO8>K zhi5qwr>5Hk5c0*9Io}1NWLDhfLC!%kXvd%QRT=HOxB)Kpz#Pc&4rAA>3R6KDt!)OVLgH?%fUK5oWzg`^pf8z9AWh)1v;D*z$~+yp=Z z?XX`@{DCL{66*3gOiPm!1Am>AG?JDRH0fHMT~13=OQ@{C3u2kNg1QxwW@%IHsgL~& z3*9|TJOFNNik!Z9F=$V9pta0sW$Zw>`y~Sk74q$D|MmRN(RKv&T4Yhme(B<6CME#!{y{7)RoNUpLq6Nm z=;#a+YLAC!^?=Fu>e6bgmaMKZ>7z^*0q}zPcGOFjP-og;b!HiLvw9{!uEyDqxXIYf ze~nD!=f>a-*wcEd`iE_{6X0Bfdffv0=bBXALg#)N(C*F(Ck0wT**D0(03_$?E%)@c zGlA}BPl*!_CI?9GE3{`-gxZ-@A3u6HPAGPj;mEOLaT@Ug{F`>w0s0Q3s@F`t6xldw zktf#Y{N)d2gA^9g1gk@APG)6e;)QecHEcAd&5q5ir{Dd9&!)aXCM^L#>01bo(i4$T zFR8C$VTBp3>{mH-(@p8(#dE2#10jOVi}?N6lKhX&jy$=Fxxorv;VFK2$iUYdE@>6a z2|gv+^kQX=un`%q-U-^)V`ihP?WgGB`L#{N3aL?rZ#;A9Pr96V+g7AC`yj2L*CzqJ zpgUf2?{EH?f{%@K^o}vog@*vhF`nQvtfN#>M`IHAhvAv^9KYjH-{YCn{&;Dtu z>pjE--_mswwJSm*F7s8U@k+m$3)xo=YkasCFU-$X7w)@VtOa_NZp$**X@Ko$EFpb3 zT6^Vh{l!XYxV_H4wi#e~mJj7`k#&SSdpYsgQ($ge+adR*%+H1S>MPSrW4iA0kBS4d zYMVFxdv4~@8{`My#AYrVJ{3@Zb^uC@%U&_p$*fL6PZoo33>Gqx04}4~GuVfv5&)5= z0-I4yaWJVMtx$rp%t!NYTMg|gb=0R>mN?xoxc{cRQ~$9$*c6I1P>=?Ivw>bX^J049 z@yF8fBZnEx8`3ww@inAqQ|Z9)Fq^w((Ip-W@FJME-ON-r)8)D4bYh>@8SK^=EYun1 zK?@;DbOeQcm|9$C>Ae=omXb@Qsmj4FBgt$+^tlF|-+ADH00?b>hzqA*BrKL@p!MXl zCsS`QXwi zI>>1;M#;HgN;8nIEFg#spslLSBA`NbAN9!B7`&8dt`}+yg5!DyIdyV1J&9$&0O8UY z`nd)Flr~v54sak}Kvo`H9ZY;@Svw)Hqf}C;yQ(v~hdD@cTdTo8yp>6``&ap{PRRV#tX=x!M^xO$1a1PL*_z)3b9M%KSCm|^? zX0ySS!GbD4tArPXsK2i{M8mj=vj(s#t(r{a4S8}BK^}-JbgB!$zJ)}86)PX+`7|KR zc45Aon25Etp^ij-Y2UX$b)mQJ$*Hr`6KNS$tw+C&Y6=^R>nR&87S%DBTsrk=YVBa< z1n}HO_zu8nC!gjP)TY*E;X6`TPDC`Vxw>g4ESL<%oG8@Bs!lES(Mgkg9%g3LHFx8# zARVbR;C;(U7IYuNLQPl8a=MIUdKLd$MC>G^wUPZHNNDGm4?oVgVjx(Vg4>h2LiieD$kByqwJakbjSb1s<;44V3#Zro3#?>uu z0uU{-GO^5xLo0N;Db=#9VFK5Moq7-VQcu4b^`RLz^CDSqhZa|poUrXf|Jn(kmY9?( ztB%rG%jArro~Mp>*ulc*5)+2X9Hux~iBr07LEA6bBwTq??>MHfQ2|Jw`l6q@npX0Lc%r%r z@4EP`rN8^jKTn@Ted5MD-XW57~@2Z$uSuRl^!RVK|eqUCGe&q&7Q0o7EhdeI1AC8x6xljyrgX?>c%FG zS``+31a-6vAUXLrgz;A48Rk(34yU(&@ORSJzW7(^6Myv2=o@KYyxXD^3;y6)t2fku z;8E=9Ag#y~>3BsojQC@OvPwdN6AS(2mBeNG_kD*>q+4#jEq(h7pGzPAr~hM`#h?6; z&!%90jL|&gUL(_L8o%huWXRjyzA|k)-}z;Zf44vGrPDuOmKCA&5^g%{gE)*P?&WKy zxAgjY`OUTB<*40ZVBEorTrMx?_tm@YAuxya6wEc`UQLk&cm2A~fmg`^y1tO@MBUo1 zLp&9Y-cG=G(UJ3;y=;4o)S~l`H`#r=W1^$6mkKEk9lbR;1`KXdh9d! zY3H}Or5@>HN~bSe1Z1?}ru3O5dqJlIC4f_DrTWNCfTAjgI&CoE%4)&U3gBH`ckQ>U zp5%*V(o=0ha2i$)}dLhwxqu6pO{PVACNGb=B9S8@#{oo9yKp@_sM3g2G#Xqfd&w-DvBGDtAJJ| z;Z826n2^l_+y(4TBIIWsmRUAY)UK}w7&ojVjaHp!J>7Q4%`u5u!dg*<_IYV^42wie z1_|SYaSeV~DOjz8Xbqpx^r)sKp zqwe|aL?S6WneVUo|L_6mGyWFLZMxDPd!R#qQQKK5N!%?`40_+Xl4CT*QF zbl_G*`_@hK6QTtR(mKn>HX{MHiwrq*ReuSfY+ZxE0xah~IbeIl7mYFq zmwr?8m%f2CqW%*!`rG&pct?Fum!JI!i?pO4XZpE#5$WVR+p#gnl!t^!iuA0L!a8{aq{o%I#){1C_xu(InEiQr;(Pa} zH{AE3G==cSveJhkoQT9-#F_Ksb2$#i%b)WV`FxpoX1_=v=zbY5;h{{!ESGWVR@TuV z&AdefJn8S{BYS+x6e;9N04s zI2c&#atH8A(UzUcZhvKmvg1Yv8kIMiU354*9Z%tATeLIcSAb%NY&=x}*)meG_O1ao z57n{J57T2U-Kl37E#5i+0Gqh#8aNKUdtZ9vJDWp|WniG2;bIeOGWWy=)nmtwhINP) z!SuV%ce8sPbR;B6z&B1TmPEw? zPm3ob@k3{9d8@+XfGgN_b-)RPpjjZ_#wJcD9C=fz1~@cQN3MDA3Jqk1v{f)*C6*c` zp*4)Us%GI7UJ6I_0i0n$hk));@RZq@Y()C-N&fKBP2lV5(I$Lf0OZAS;l#IE z#49RrvY^GFKGe08N~(I~Ce10p-6Vj$wq_+DojU25Uug^A+m#6qAG2)H&RhrsrEzt$OFsx{1WGiq((7 z!2@Y^c>#U^UYX?4XwnRmw?(AJTTD8ps2dAx^6u^F1#mY<-P}Zy*~_G|4RxE?Y)L(c z6)#pg8Ub__%qu!+a=(LobE2pYcN=?4oXE5>aS=Q#(bigvKvSOU&-T7=ALawelN(uG zC9<8jQZMlpQtLIO+Dg!^$DSrUIXMk*XI}~RM`^rjRjQ=9Nm2>3e00*SwHd+Y6l!tS zOVy(~Ss8fKTW&*5=Pd8$s6S2;)uCs?M+36jTdvl*0I^Y9s2}|0Z|Xex5w!RBb3!eB zKnoL#rG*(RQT3v#RF}SU|6irM-|^wpKXN?HH@NcV1d9wKUUss}dKQ{S==7tWQ^QA9 zBje8LQs1rj&StHn!6z!PK(=^}RUTfrnq@^d&ZzLgA-;I9-I6SK`7btH@*H9GL0yk@ z^%ME!9bxj7^J-}6^1W>>9>at0sn;eTXH1{+vn+#85nt{*O1_yF+kcfwQ>lUy5t=^z z=jI0OI&9&yg2ANpmb>1Se)^*a(=$K$M!NAW?@g1`4SOWR!~tNWWq)A5lP~khTfVaN zpOh^?-T3I&yT}IjPT!#xC-a!@Tj|a zh}d?F!8*F$+^f;iBBcx|63d|u2m&D_N*$;S2p}DRLdu7PrU^9x2hynp)fND!`)=T% z5;oO!VbdAI5KhDgIB=r>@NMZZC!lr$D9)WZogR7U$7uw$m=;0L%$0QU?3r}(i6_%M zssau&Pd-12$_kQ5x{GO!obTjJcsGXiBBg629h=|=z*TdG4$exbwKuMXhBY>(we&Wp zGnX!6F=j2@aA+`n^}7$E+YW$3>Uj=xjs3lBtOaCt@%*M6j-=BeecAAOf}7*4s!J9(e&8E52dq6VTYJJc1jM~e8f zr=Mp0L88ig`K9El4Yi4R^llwI1(bq12V4QWlGsT$$_f&ip;K@y*m8v%8dpf)0b)CKOQ4Z~LJTT&Zf*z-X*v=&frEp6oVrMQ;*^;85U;#+^5kYs%_l0+ zZKOOJ(GixvPLoW}$&fTidkn+e8^3OXAoHx+kE;g~rg}&CwLU?s7C}ru{8=ZV!F$as za+84+c@wC+kywi_+Di{$1wHZtm<71kY@yO>!?5|R0n0tEP}DV{{|uU?VQoTUD>~&Vni4DZVY{u-NJgXb4fa+#cqy~tiJxz_Xae0jzh?uVfd+f~h;Ku^G z;Hc8{b^wgkv&Nbd`QDE?P-$J$%E~HmbPbC%)Q>SXbXM88-POT{OUiP11f6mK{Th>p z4y4v*#8m({#jdhibOrF$G8 z$-8yL$w6I%fR2?hc%xpqQhE2N)CuO9bh-#?ON9q6l^M98uOA@XNk*bEO>vz{V@QXQcRkg{TngMJ)`IR+rx2N6r`Aylh>}Se$wsU`N_=21=P^4vgCc}5( zUdE%ddWELy#{oKfp%djnQ+3jsJ3WRn`sYRfgAJcC9x0rI8Y=8aB^Lm_A?1lqfem~^ zN75>XhD@)ZCUfNG^v0%6tUGY@Hd1B**ISS>pMLfk%sx)08*e<3MlWAV5B=ze9D|-1 z@O#n!WuqRp+n;;xRGPkweP`&ezBr$TZ|Z^8t+Ysb9Z2)E@2pykORAncS%U;o3lc*B z4L58qqIc^kdV1zuNdCG|f6~lh39X*{u4md*70#k+wd2>TK#os;IuYv;&b{M88vsUH#- zC(Tj=oxFK0yi4P?#HsmAZdu_0+ym$YFy>i1-C`D{%}#~{piKa4H%Kp{ZZeH|iplBo zl>cdB7r5540U035CQ~huEc9dH2i1`PQb4W0Zq%l+{>O-p(VJ#z~VAZo&gl?-j~)`1?XY& z)`}&YDJB3bY2?uUpudF`j0Pq*ThmKno={MkCvLgi1?!d@)fRUqPXPQ?CR+*uSWe0H znR13y%B(%@KejK849=&^Cm&2>Q>f&in?G{wwm3|v#`-A`V^U(Ac1qT@Sb>2vkq*xO zT3f6mPBg;B2F1TWW&7bpzQQes(Ua||1RmDWh^uIqf^*wxGz!A$Utq3UQ@pc}#5GS6 zpux6f7!xjN^fvgUi|q&ZoDOl3PVkTNmT3Xvd7lAsL>%;)2S2utHR~BBE!`wu&wZ6X zps8nn`uJ!6S^C-+{yM#zex@6Bp^6fB=rcXTM*NY6dB;t<%q{tMLgLiGORv%t>XhN0 zCmk#V#y*4Yg9p-ke*69D>tFoa^s&$UDaHesEE7i_N#usTq*wm-PS^b94e{G?mKWZ3 zo_4~NuXoA zz3;BLyQA85Z^s+G>2`qaZk;!Fn51Jo^u#4)cFOqxmz_2eQD%=3CM2-XupNf>5={m_ zZfMi1K-7(ht?gY5x^-y=D-44ACM1=EM{iGESjK1pj2s_4l6nr@#0Jy3bm-6_24?hM zk-)xi>RAppI-71eff+6~pg!=;Z$ZXZ0Q~8x$uMyzKv8XDd_wbmHRu=jGAN;U3?P#T zFe3m8DKLOz9+0%*s1Mz$uBe7_o{ggpOoHLdU%em6V?CgGFkL3yvC&HaKkW8yTuw(0 z9}0jtjO70er(p_WR{=dO=r<1yk09k6#osy-_#V7i-V;(A8r@`xL=)hyuC1FR=TME%Y+VoxeNEVA7eG5;3*Zf1f^+jKMz9oxS>{R5 z90PB3#2J+6A4~&}(2pj7ste1gCIM9)0}QEpA!yNh zPb~mawF=8Z-eww!oA?AnVO_?ZnbvPBK#}jDR%b4pLbXAamo*4f&0!0GZP~gp&W*WJ zSBHMF%kK8y0n`bo4!kFunl~fz03db7eN=xrXg3X{hsN82>WJW0JNa4{dXWv9=CHbc z8gT8(g%)JGs0Tx+!n9F;0lOro{__kfGh>Fgwm;QJnBGeOuXyZH}c%G7g7tW z1Z~hb04SunMP4k|0C(P$7iAKWQ#Up!pm9`&S~0=u9*H?D?6~5g@y}&eCzwZLB?sMh z__@4zE}j0_4^kgxp{3Vatb}ZCvA2TBQ!O9cyme3Maah!#vT{dSeCSg{{lf{LD?7w5 zzjK?Rc34s3nJ+83VU3GFrQh;Q&>W8TnZC@)5x}rHC8Ka9C003z(=?)7eZiF_@Jh;a zy9_=+n|0Z?7Mt;T8O>EcMLIzr?gHcp>%@R^w(a^(cXMk*aX=ThRpZ6g2}uR4-al|K zedJ@mpT6_eFQ*S3IEoq}Wk?*+D@41GNFw{vYILqj*~ap9<+^owxm%grj-gz6Ubj543 zynRI(Z9nCkm$;SXzRRC}Wcv;ycD;O!-v(!c7r%&S)@`}Jch?>Rb1Lz!g4|7JI2VPz z)8uyhxPE_~1Fx0?*EW`G!&~8gPK7MIzNBN>j>3jkRM>5|@`i3MI-p`eDbolyyKXLm z$1h)wOLlNdQ@pvVlMGM#u~h(Mu1PbZ3OfWka5{9&_bF*=Kms<) z0T>Qgbx75lG5I#X=JI+|=s%AfzcaOV9RRE!NY$G*Q}2m80m{pvCbP=2e-&#J4}brG zGZd zWEpA)Pe1)E&qzPrTu44D98rGxu_rl9CZEFC321)!k)IKln`;|Tm*G@YB#du;%iVyW zHl(Q>zyml^N-Hp8#03Ou%No@Lmiv`nsw?cs$GFGIiOZ-5VR?fv4x}Dhvy60C%M*I!H<7E7E^)ig`lyHo%%ir$pO<)^f8hX)><{5F2)s z>8u?zvw29RZjxv2`2e^aq#gJP5pe(wNGqGMYSDFakj}?ZHD&wx3U^~>SNY6a16URDbD_GAE2H?qE*u5q%PHyxV zcC<}{hfsR}y$B{M=vl8IEw3Sh6#!=qo1tr%C^iF}8=3gH=~CO>OVqQcpFIoj*n`0& zOi6OLNKsG(AgUXkC9VqUT@7qJK8TuBd#J0Z@^bF%MfM-eQ$IMd(>%Zvb-MkXou1>P zWfr8<)Gs%qss;pd0ffVwkg8_;J7QXHDY zM5>e3i<-q+XrE6PC)wD&v7GiX0kXZQ{!m+);E<&UQ$3b=20QM-tv9vz?Mtou_N5A| zPj0+aohMX7XggkhN8bZ-%z2d-)5qNjo;QWRaO6I#is)GOe`Hc7dEpiyx_mD4Wqf$C z-P<0se>+z1?JfDH;!B_D`#zH9MM$7^Dg5f$Tf+nmJOltYZQSF;VqX;=^=BS8n0VDM z)7OxOYcN&RRiK9QV3m>8iGktcsf|4k4}R_M(z`$Q=``aBuK1}5o%EcBaoIkDYU}#0 z%d~xGx!I@XzQ?-DWFN`6LXbKps4IB5=iMJl_y767PA4CKI32&^9_*Vd5XqDprFYj} zJ8q_4Uc2vg+wqlsz*7l($+eT#OI{TY*M=(Vf@xfv%Fm1Awd5|%yXoG^&$U!uoAYbm zzq;qW1?Jm@Ndu9Nm(W@`?9S<{qxZV+>l`RJKwYvPy`+xqW>BtZ_FdsEI_cf-ueslT zuWtvAd}Zg|3GLjM0DappL`CfnFJ)&``sbH65@dUu$;-s?;6QL~hDzGYm_nM%KrRL_ z0!?h?c)}sb2x&5pad-?;&#apafYVGG14m~MQpzQyv5I7xy8$|FSa3n#6wuWI=y>yc zKMo)tO~;NL!GAR!e$#Im4%(i0~i&WxT--}>5D(;${xRNZ;x@h8$Gz{GUY93Rd_ zx#fG?oE7D1HWmjG1Vbo1|zsumI#^@3kjOd1&M zC0(8TDjz|oI@nGK!g>p7%6s!26Al7FD(v7Q@fodym0$pYUvnoBR z$Ed%brP6{d_0{tx(`NQtI0;#W#sN31688*~2-S0(d~U#lc2*c%@d({;@*bzk@+=Rv zNYo7P!j8WjjrxXv%BiTR0py}bfwvy=q`H^3&KCjt?sb{N^3MOy-kS$&dR+H;efNEx zxw8*20}wk15~Lt%Aw`^)uL67;v`jes#300s!~p>@~@Lr#Z_^+6j@$Wv9%;p zWLuORO0q0QmbgfiKmr6nYy<|g-?=k)-?u!^ufO*$g8>NvR|z4`JM-P|{oc2i)2B~& z|N8Xl)B2UyET2vvnzmy>OjoOa(q&~pjs2hCos>+`yEF-SEI2nO z)AC^lX_yacY<}AE49!JaOgEX_@SJxs6O$%*MdXB0gK7E!^rqu;Cz5`ckT{vDk4?+| zrl##L*C=OvE;ZRtU>N?npJ9VZ;`jXU-zY!#5C6yVW0)2m zx$}`SyGkA%Oq@u^$#$KfSfYBD@|aq9X3F(y^3N~g?xks$I^X)HUM5jjpZCRXUgx{X zr-`)_5`H-8cuQWA9ogKapVI6rO4@|Y;()FL1?G|T5M~qFf9?Cd@5ladw}0K{!0YFL zg0$OMzE(U#Rk3Tre?1}GeZL!be-0S}cv-=DeOJ4K*v<2An;lK5SR zmF7haV~1`EhL33k2{HE7E1g{v%YmteqmSm6fh2<SlLBYyGH1+?&|W0$a07RUO^VmCOA>o zv2S}Xo(vZ76vC0~4CGHe{zNt^&I8B~VJE7#)%w#+#>0j*fbbw8Pg`98Q2A~Y*Ls?I zxcO0WljVXwL7LuH)X?@ZXzD^wZyaryZKTExOO(z1h@g#Lxeo3b7AH(@pPA2L8TyHH;MvWCxN|a zlr_^?0=OSPaWueMP~Et*^w2APgmCSk23S1bh2nV{lYQjo1@mJ|3Yck{3wTjnX( z5=XDMGb!w6vf#vSoiv8n)V(mTi45)?nV8&AE*YH<={?4YD!g(Nt;~lke#3S?aJ9++ zl`DAj;G`jJ?E5*fb@GbmC16eiO#_@ldIOC=s5og|#~$8^+h8v?|J0>Y`Qnq0m$~bg z%ft6Rh`iNFXLT<~G~|h7Xp`2aA_^VIP8&Qdc+^q08rUv@RikhCR6BXvvXE0B@ZbKc zH6IUPtNogF5Kad?`Hvmi9e7GmZdh6=rWcF^2&vLL`O6leQ6=4wdiy@QT` zZuwpnXt(UvcPm!yv2GFe8~9PHv^&LaNR6B{Kk^N?B1`)ozTqDS2+R?zRB--Og7)5W z-xTJpSV3=?!?z>;C2@czv(su`Ulxu08T#wZ{A-E|FqtU3^+D_J?yvnM>^|QYoFBf) zGNs@4z_0U4iZpnE*Vm6Ht;A^3=e9K*wQ@5ZF5f%YNENpU?tq(T4*VS9-9H>Hrh~m; zxSLISf{JMD0V(~*&+;6XifC7l-T!FmVRPycjum%!=mBhgyu6Bi_*vZCT`o_5 z`SCLO%DHkK`}2=~?Bk)sCH+~BSf9ln`?;6TmFNHPNr?lE^p|_i9s^_ylo7y}lY&)k zt$COH(UST5#q(G290DNjRJ*o;XP9SFmIbteYBTkfJ~cI+4XKC5@F&j_|ChENXM%9J z^v=(+N%mgA%2avg*=Ngp9(#Lv{`u$2F*c~W$@Qz}E|gQJj{(s1t%u2ynzInxqJR&DUpYk2Tk@ z{}xmVjx(X79yRyiab&`KBII{YU*)~!Q?n{i^#o3VawiiTO_|su$7{?&h@%@x$7ok` zlrJ7p4p(ugSAGaO1r$D09xby#IGT9MvIekMt8VxW%rP!syAeK^P8XXkSI{nd$dPSC zdw0_ijHnf!W#uHl2&;LC$gUo7kEo%^zKmBC!J5A7hgmVY!i2H|{wyr8sTnZj2J1>Q z^kEyczTt+}PUd&bshJNgP2Dh9vi|li{}kcUU&oNSqWGzlhBAm2(6WaFnmlA_gbAz27q+Hue#iClncw~O^8O$B1d}uB)KCPq zP9@!(`IWL~CSzW&m8zhoP`AJTKjpwqKk$ZgeLaonwZb%=Pwjr} zGHYkV9UY?`h3In63_f>mS!6n{D@FvoZiOZGW|6In0K|FhC z-;NHsyzuNxY_=OL7qO%F5T6-0OFd&yzt_S;l1|6i>x-lAkxKrh6#ptzc;1@lkL#a&pU?B0AmMI zOhEu|YQ_Q+Qb))V>~BDNDhj?UT-KR&Am0VZPLKpIYXEaE&08#&!3884q{Zom=Oe%t z&nPo4A4sH(d(OJWxkXd3?2#pFXhl^b*^2 z1j_gV3MVs;?8=y$yg>l8rWa0B+^Zm+0l*Y669mF2e!7?AHt#9`MibB8i5ib`0wAwl zxmGT&U4)kIa=z;#bm3Kq`dS9CJ28s?bW*x}$A?(>T^>CB!GB)8u z$g`ehvH?;afHV8H`qI7y*=i&`%u2d4U$NY1kNbK%F!>sdhI0sDF4%Tu;p(J^{vZ>Y zWgx#@m`};)RVPzJYvtnX4P=>B823D2CZo1q4?1f3`#2>R0SujK+vIJ&@-YnJ%X-!% zW^{|2mYd+zz zagitG*mfs5nwjYR#!%9r7p=aLL*;M%=uebS{@O2>PyFrw3i(EXU>kEXO2~Mw3r#g0 z+hVfX;gvLzqWSQh$p_z=AxN#@8qY9mm>j+1ecxT4_{^uump=3R<=r3n7>8kzI_D4rNXTE0G>SPVQJ4gwt z7-&EywHkzMHK_Dv#g-RpnmpEhS)hYPtFxoOoPFe5@GkInK+Z<_@)!S{6Gg9;;~WZf zk&T`gui$?gkfukBzCJu&aKhxrf97wNhu`@qo@M6CXa3+1$PdjfU}Fx?7=qwQ%vZFX zonwF;#9Tph3GI!CHP7LrJHeqr03x6}KCS05WdH!{Ibn&-q+i9)x=Y?e0Ovn{{Aqwu zCmRf(PTa9W!yNQ-9`L!&sjO}Y#H2!PIm}sQQ{V&l-&a2Og+DE4PMx%p%2l+i$M8He zgZ5vLjy(&5IGkg(L3`w>sq4^!O*8)%0Aun_e#?LDpIsnV!!EF7M0QwZFt4`T2^6hm zjU$xQG)6dJ?d`7?I37d{zc?#sNpp-NN7?3Z`bk+r{+&B3< zaj{N}ACmbUKlBm|h!%wTViO>5`Ks)@3SfBR!9Q0poM_Cf;iYG5F3&Grn#=@3K;DOD z{OFMhOh=aZ%mtw8=8)v$Puzy*1Yl`-K4vlUqf6bK6NmHXFP8i6JA-_4o&f2s zVLR;Jf_S^(JO09{!W3IxSo6>jbG1~hWd%SYDpPgr$!>H~|!mypx{G zlULv!b%!a96T+~y=@!!mP?$TO1t%R;||cPlz!D&US%80p>y{jCV9NF;U z{TcvN(~L~wC~FnY9a2+Q6?`j^HBD{;iUr?FiRD3<`4Hb=e5Z~{PhcE`n-`|MAy+Pa)c#Z&c*Z5rcRxZhwVYCv*G@_; z8+o!S*a5FK=b7dETfX;)%fI@C|FxVtbw_bi{}f)aoTSTHFZpRdlDzWd4g5(`mBv@a z&f}U_{#GU>n7_?EC8&zIpDqojrmmHb-Em*M9gLqo z6Oh(__)gMjLmP>f6X195rRU4@UwImnmpjY!mGk94{?TW0bov?^%Invr%J~adF>&Ge zcYxCoG?$uM2%_3B5FQ1j^zd$IK$m#BxAW+D?7$f$)iw(bE~A0HfSWxx40=F}o?8Tb z@=X(&nb~Q8nTJCaG_8Z>Bn^nGUn~zicu(TJ^2!C6fICJs%ZJA&(3;}WhJmvK z<9-*>9YkH))-N`y#{eZYnj#zPr1qtO3@N)>MFEUJRbSK56k5;FWT?P&(o@qGyH(%i zsvtsu?bXz`no!MFOh<5NemiM8s1n@Yf-g1PP8yo`eSkgp6f837n4Fw~1V9<)PTKXl zaj{9$L!K;i{3w%PwC~WR?YZFD37&DxU-G0IDT#JHcIx~VaCIX~gHAjsU+ndH7MpIu zYHqceZ~#p-m)ydQx(Xp*{AJmkWGT68tBc48?koXUuDIwLQef@IRKau?CoXp3Z@feK z!PLo3r_$R2DxPO`MLr0E1?zfFF}^E6`Y&(W;xI4lx#htsUNgS>)eD&52&&f06((m+ zE*&V{w{VpMwZ<^98pIQf8yGv8#5pmM1`o>`9vdwKt?iZSO&k*7yOk~xvMS{(u9SfIHGe`O zkBC#A``&!h|B{#a`9PZObMq}dCQ_+SJ*Fn$u^y}R)S@=~;m6*Gd~BD`{Xf53e&}cZ zGZY8HtV}!jt2}exO5Z}6ub&o-tv_luDRUE4Z&1c3BPQRNboCCGkNoIQm0$nG|E>Jg z&rX!l@grr94ba=1Fl$I=%gNQgX1A2wlEhA-x&4|~tGr(`%&lHpru?ffEnm&}R)N29 zpGDR~o&~V(QV{OLnAV%-S$-bKp92Ht&XN`KmgI)GhV%#_(q)@zo*fAFw-foM;y}df z07~2dlLJUZ2bJ3X2*1;*)D+Zi zdo^+VnONM^wz`{tD`?uU0NmVtmfe&GvlI6{1XvIhwU<*2Fah#_EX>+Dx}6Py1EYlIv_u9upi4P=_90HeJ06XD`$O+3 z6K{JAfYBFFw893#FMsh1EXgmGi6i6X>Bql>U+8DcX+ZoOw&gEB`*Il-fNPe5rgs3( z7@KJI!A58(0hefB)m#d|1alrSZyDyvm_3(hX^OpUEP)a%F) zK+mOqY+Z8b&S`vDqpU1XKJ_ei%Ohw|1(kv%wEZ|nR?`fm1q(5up)(bzb)k8WhL+NA zSTB66FM&co;5fh+)(f(|tT$H*dP&p30>m81#Jd0;(JyIKz>T>Ao=MbB>-nQG%}@*J z1i*t(3@fPhBdx~UrmSi;uUx&H18uB%JAXCZn!j{=Pc0e8iH>muZSJU@e0Fp zd95i*JK&KTNZB&^2$9;rSDALh@`|>^2()(Wf^*Yv=#yUoPdpQbSYM{=>_W|*=?I7f za^5=;h#3RV<`!1iNPG!?J3**vYI@}9tnMlBsP;anJ+#W~E0 zx(hSYOa_?%u;FtBxw?MiGCAu-r2}_`v#E!Lg}L&s2kvH*U~jn$Z9{CRcESdmQ*X-K z4OWCyq*j0sg%vV-%>$j8* zx6}=*LW1os;wtwZEam=(5$a{Be+gi>#3U4;?F7Joq-UB!eG%Ty%FoC<8 zmhYO&-X-bhwQ-yzHg#X+z%z&h6g-@y&cryCT3Yt`Jg?PYFy|0XCqFktS0 z5@EOVxCMQAWC3lMZ-U_4QNMXPpxi{Z(m~ey-A2F57dtzz^xyUNroT^>vuDJ)if+&&EGqRlknX?t$dBS%LnyrEl53oM7gjNVpI(YPSNL~>b0+Js3wow4mG8;V)l{+4ItUSU7 zzmq49WoVnhpR=Ykt4lWkFYDzCfA(ZK#^E>XEZt9Dxl|T!;5CGSY-)C;oIP=*=qpCo zcO9I}IEO~E9WNqXXot51^XqsDSy@b{eEI53JZ((hcnO*~0TRu8ILpAgxV%vAI&%gv zgC-MTXBj&HQvIwRxKVT!d(=ai&u9uV$%a)A%W?Ck9y`cBHoOkDNe{+b&mJ@-SeB1# znvwwYd>-v208?{}Hnp`RAn*_c2%K|IngQ7+o)1+4+6agiF#Ajdz_%d+r1{njq@GIj zCoe4mdx5rfDA3V@+UM1dn7SrSm+76bWD_O%>z{s$LxkipQKQv#pMkVfpp>Vvm5qoH z&W*AHa;T3vlHO>P124hX3Y!_tICS&4ACnR{oW?GgL`=KdBlD8RC6(LGjN}RQ^#Qu& zlWAx+vW#Y&NM*i_!>ssLoISzxFsJA`Ayb2{c~m#`HpZ!@OVn?trdZUk^?Bj>7jWT- zhafz%JoDUhfO|ZkFzI}d)f8>e-6T4W4f`zn4m78E;J$lcL>roNJh#j*a&qkV?n+CP$mdUKjBNvzlTou~ktR6U{%royu=fCn^dk9;upFYLai? z366PAS+>wb8>qreaXKKXCAMdTtJd0fTA}yqbNjVK$`PuY& zbyC8!$U5H?ruOs7E!0xidbx65x5|dbfLuoa06+jqL_t)9WlVtH`R(s7pZevWD?=y{ zj@|cInQ?U?xmg5huoQJg!pW9i7Nyg?$iAjaDD(7^-(Hr>_9#qWU_ZdA``%gJb?r*| z&0qSb<)feYyG*wFvr$@3$SHGaQb+==KjESI_?tiGS;N)edCtT}8o?KYOXQu0t>3M{xB4=C1_|?Ua5L_1e4|LCPrNE^&9ByA z>eKongGCKk2iArUHHLrdT`PEgHX8~9#4~gy1(lt3kRr}HgOhQZ{xIK+f|WLH*ai5| z19D$cQwpfj7Wz6W_YRhP$4+uU5L$3GZWDJODc^D8EZ}4o6O-}s$Om6y(;S=q21m>C z^!4)DKlmMhB-&BT4LgNw;S2Y{48RZY1YAMMrp(M~E~ zG0T9~g(b{5B5(qvt@0<2Kaa*zKuWiZeX)bMUSDSCX3@;zF=GY$Xg1~v_O*H5Mr$b; zue|lQ?5liGi*8x8Llzs+0;=*dPl~cJt=~_W3mk`2ld9 z2&m1oEPhv4amk8g>*P!j>>)swPgjfrj%t2F8IcQ3PV~WV{(^M1z?tN2NE@sM(Ays< zUuxE+$B9!WFvRz{vZnml3furISocXLn%qgB8&oe{2Cx#xx*uZm@dlfaUwZx}CLR6w z)jv@>hR{$?&zIvzIZTKN*q5Jsw%qf!JJFlBmE(YV&2c8N^X`OyZUVl>M&~1k$I6{| zoGs^HIiJnWciefpY%XIbPd+_>{P=h$zNeLjr_`l(&0T7>VJ0;O-=!D+F+uYnDmCnS zJ!%6W28crcFoy`qw^_jc2peH_kvWT7#sN*TaCx|Zxy$J@r>Wb%a(Rw}o2H1*N=A4` z{<-8M_xkx(<~Pt1_hXa3Fg?lH2j|PgOfcitDXIbNm>n6^YWD~lGPZZ?)0E?(Pq1?KrS9%<|KNRy@* zs*wL8ldT8d^}h1-7d~5_`NAKThu{ALH7NYXDa`K>xb%4N}hNf{~u3Ho( zLTD8?RoR5S{e7DQUz-EAfjVTiI+#YhUL{JtAPhY~Z7jP15@k#to}{aptJTFbt6Q_EI6O9L5)G%Xk53~CO>G5-D(d2#cV_~BUIv{K9c zeoA*E?tR{{({eO)?uM*)4K3yp`Vz^UYHs!ib-5NG2tW$JY-Q4f&xw|Rw4JL}UQ3%Y z@>`(fWt{rWfFsWVkMgI_4VHWhayA*rGSu~rm-+4PGIHb;106Q-?YLe%dZxVRLq8Uq z(IeQr-|>$3l!Y7D%9&$_%EGk^Y(o81IsfvD zrWsH@d?jWn037XptrsvabhFG(I6s2$rR8l-JH-YW;yn@40XjUTKvmgY%}6x2&=q7h z$hHnXGOV`M+DT_KAiUBv#f^LN)6&Y9*mLu}hNhpX4g;ki1wOH?9*VfHphfUnZM38D zc4-{CiAo<2=Yr`?qkrOFxU9gNknlu+ zgIzPOB8{tUe;Yo$EswV7%xDN@x{fotr_?}SP&cz};-tszITJ;&aEW(G5mnHK~4wHJ4# zW}cOSb|w`wH|DWH=fqnlikwcn;9*O+*POw-&n%kxyY9LhbCYE@5ML={Y=-twr)A7o zIxtD;#oR>S{4z7E2nYj|AXkD^@0D-L0{FG~mB+f|)T;{a80Ycq!hH{>Go8raG#YwO z%Itxjsq6ZL$FvL@EZ?E=!%Rv%33nBZKEN{R%hJjRWrPe{7keGHfDi3xw#1e^C(Fhk+qTo~?1> zZ5L%D$JD8G#hipTri=)!rK2H3_mJ_yZ#eQ7{nZf4p0aIU2xD^9c92R=sHQkIwFZEv zuT~JaY^T08RVRjI!(EGp@SWd;bp^-APWn8VSh-(ef<1ENc=_NDezg4B&;O(HiT~!m zFFkmxnj?)WkJf8VJ$;qAkVa&VG<;(jG)L2ikAPng??ga(kTOGQ7UN+{@B5*jEdSwC zzgAA3zM~vE`}Q)$idruOOM9Z^+f-Y>;f?5WgF;BzVtd z=X|Rx&-WD0Hnq30Wt=0b(M>@<-JI)gx`}KkJ~dSBh}35_Pj*A!d{=3y>Dy@~u1Vc= z?JrBHN{HsU8cN-*1<8?$n?}{HI4EtPWnBkIYV#fNC$0Y9laIqw{b~Js)n5%w{t^s2 z_(>(hXYTvC-6H*He|fXCrA;p$Rd6AwJH$n7ZI5%L`9?ct3M*Gi_vTD__`bW)d=8Zp zr%!X(&1!k^<@01$J%U`id?|jXhuH||@$DC`&PD_3=F$bck7z?LO`c4-jV4d6w3=!+ zw+TKpl?dQM)4M9Lp z*bs38khTth7j(sx1TC=jAV1}=m%jz%hHb8Ddkrjtbpjp31iZ6K;Jx4>{N`TzypLv? zZ{@TezHO9ilUFg98qJ|gYiRmCJ$04My90xqrb_$JpLr)If?mIV1yiL3Rvv(Ut;&gv zq;6{U#1w<_$7Bh6^)5D=x`(+96BdD-rbY9tJ}u4vQCyD>j-TL^N?db}o+xd7V{E$^ z0nD-i7}@P4PJ7E!CiCBZ`Udl_7rBSIrmoLVHp!TQU6h zliDO~Um?yr8HF=ts?ar2%2|@(6wx7s5DFxgkM+&3+COk~)(5ZXN zJHGwB<+Gps)$*ZF{GBpmTWGZ*%W9mMmzlTaZq=(~SEYr|LZ#%Szkm?#dFBJcd!lmh z$VB<@5B+%gU#_t&_T1knlvQg|0VE`TS*al`pM(9#juq^|$m@ zN}AASZ>;*Acdal@xV)`F67H?~$6`j#8V>k7|5AE7u%^UszJvz{%xO6L=?c}MFcn9s zTJ`tlslJ`UHx&ok0Tr4S(l6FdLs_yx*oo75`fU?wbsgNbI$Jxr9Zofkrg_r*-U;wJ zZ{AG2hEB7p?V&bwf08cr<|0_rS=Mf_p}FIn_|{KqqD@Xsn0=jHB_?DzIJz-Wz*x0jwHvGhL5NNg z&7ftZhHob0VCQgTEXP={a0oI477mUA0(6i3!wwp4UJqKSo({keoBG-oX3F}~BsS-l z$|{;prKt+$+!nKt=QW z!V52>xtwF8Rwo-um(b7x>;d4j%XkLCmR)y}r*P-z2GS)De$h+>!Z&FLn$xTSLT5Pq zrV9Xe9c`?#qiwU5t_D>tT2G%K9_<};2q*-p64{O`Zx=eP+W`HM?j!q_SnW!Nj9t7o~NE?~}0n%lF zyDjV!HFc8nwg2;#bYd5etd4VN(IlHn$MNi9D|qGHg>sUUcF*2%XE}fFC1j6@E)z8O zOt`Vu!;UuC#EcF*C#iNh5ngc8L46`$e37!rI}Ii;zV!@5jdHLQr=N0w(fk}UD|oep z8IG`VjYw`nUR#+h9onrhIk|E130wbP7 zf8<79+N-D;b_|I2wayTwh@7OT`ADUFARP6%+2Wb>%b)ZR{P3N7A}md~)_P_bswOh0 zEZU}&X`cy_oxs%ad-@jQ<~@0)3Bd+6+wqQPO7|2Q>B7+VurA!-t*M9vVytS>67=BAHbLTZ~f9Qlpp?2 z|Ld~ZGs2RYt7E1}VqA%zB$|Bq0cX;#c+~L7u=2yd&1JV~==!MjR$8Qqi&XOM-_XxZ zUin>L<^5abPc5N^kz(6IEobPrO!Wo);74U|UcK2r2M5e;T(3b*?MPd-;@4{he2w_G zAAfUmKy9kp#f*=1$hHBO=8!Fjh6Yn5tkrm0;`VCJ?S1i=A&92`TItrjrGFPp zHWd1>xoLXXM5V>o-~`7P5hk~%j`)Vz|1J3?yH(xq^2gD3YyL9aj?XUd>Rr@8-$&~^bw+`w_iys`P z!$UQ804WCOwzcW9K7XyOFHT`6d)dNri zH0UC2a7f#0k7(aO%kM!y0?6+2wtLP7&}p}R^yoPLvRBFj58Q|La;AL#vwspF>BHFl zKJ(1;(L4_HwsR`!Lb-vt!yxV$^=C~h$UphfgN9a~$IOS^)R?)^Q=9co!2Ap+Mz&+h z;-)fP#yN2i)L7oRnc3KT`>aX|QUj};o@Ag&9GA+yjL*k^jLMN)(lK0sHtjNXFK?mC z=VH~|kxtA4NJXCezOH;!;3MEx9z3m78m*wsl&PA$2!d8r!;+?2>kVkHlZ%>XBXa^= zH)O8q|67ebX+*;=oy=F%d<){uCz?s#TlNt=tt{hSliQTlyhTEBD=81iSDI}Ut`6{n z&35{rZw^c14dJS%7hj{vCybj;L#LD{J+TqtFfCzMCdzuaLmHW&VD7NCwrJ5)cUSQ3 z-@~b(Frtie$d#J^zABzDg?}506Pbac>%Wb!o>Y+ozi6xan5Fc4XoR z(b~$4%=9V~4SvViV-`r^*!VZQ|@Z zk?*5viyI0KAhfduSkro~L(9mw8y=M}DajSk?5c)z?DipzJUVfzbQ6RKNzeF%hwNMy zE8yI;e2&c3tIAlF0s|>i_CxZ5k1}rEl8$C;&6PF)E%7-{e*q-U1bU6O`L6Hz5l-v; z*X2)s|2NBfKmHSJ_N3p)cxK)e=$oHy$N!Sp^kYt1HbaJax41y&PR*m zJov8i?(5ger~ln=ln?&sPtz`FSA?rms$E(kGEXUiIkou0GxBLJ`Mz`Us*W!_@wq77 z8~%ISv`2p@MVV_U_>xX(e5+kM6w4}0Z-v*peEY}R@aZ1T?!0@WHwOpIZ$;KlsVaFp zg=!VvtE--^*)h1|a(Go5?aW#@IG1@r%sMClR2|26HD1F0DC%wzP z=I!p2{de^}UE$8-dbj&~=f%Do<9)p_`y#Z$e{Fhpnhi;NZ$B5Xv@fY9-xduF2zDDa z#c3<--%RXZhk?gWUnk&oa? z&1{yowa3L&W(=2*YU?K^M$3EO`>nVgTq!5;8gtLxr&)dmKr(62q+#mD6rMl2aj7?3 zzIu+6P_YwU0!*&r!f(JsU>MLf1zN&fM#-bj zK?9ltO!)0UFW}c5o(JRzGEGk#>}D`uw@iXnE5|A#j=)!Nx&ePOSP7Og__qdS_~b-E z@Zv`x;iaGLHq07G!h}-Yrho-g2K2ljsZ2{Ok6Ds#wC>Qg$cEVkCQxnyHMT*j-FyYv z!Z|^jW%AR@B*)E>_8H4)6E*u0^y&4*1FN<;tuAIb$;a~YTd>tmJn37&cD)1I&R@93 z!~$1~ZqOvk5SsHoHXYAmCbB8`Pieyp?$n^09?t=Li=2GxD$3}{Fm~`gdwzu{dQwq7^gtsYmKV0l zAvR7Q=49Sc?AXs=oPxLAN$UpbEYWU;2kt5x&@eg8gb)&ukTL=P=Po>1W-h;2PMm!N zTkNxCyaStjCKcTs?1~^CvPlVrtlGW{zTNy-C8W~h#HY%dz_~>;UwIB+o%~ed2~XbM zr++gj@65x?D)9h?KzhHE$r)ATIKv)d2}m5}SecEvmwfS^FP&T}#@w2gwa$-gZ+qlDtS&GeMm8-)Xi3^hJo(ChPhm~6Ce6oo zY6$!FmK4=r#N8&ZSGhO+;u9g|XQtk&N&PM-PL{$@|8 z`ml*-jG6*BTt?tY9{GpgtuLQ9{aph}efH{$2z#RKhONZZv|F)6$p+_DK$njVmaB%} zPhUfuacakHdQF?M_-+Ai zrdi^j2O#wWHZ*})!&X$Y5J5*b%lvM%L`j&9dKHuc7@}E5c91dCin^S9LscRSjKLSy~s`Xv- z0ysw=A}u^-;za%(wq^c_C)BW;7Skr;c@J|bW^BO)&eTt1gRanQYx6)L!I4s8@;(x1 zY7#WhiLxmdwCMgaeFuS(o=H>AWi;gaV%G(tZX)X`p8#k2pmkPGq((C#%gmolR+P_0 zwAAuLuPsg$1TIdRQbhPKM>X@PE*$NjSW%$P^!nH?EhI-*hPjC5PwaPK;x>od!%phU zeF!isfWBcucupJBhxzgfyg_r?ht2vZt{f+kD-VL&Zkq@o*TYR06Ng3W>d@g4WN^4l zUS$#hRU2#~9!4|24LBAKMq>-@R;9dh5+bc51K5ab=EJjXCK)sMX&(S=pFBEI=9m=d z{!}wBCoH<3Tv~LJqluCeC=#6Z%z{AxcPTGF^SRRX!V}2WAR6@hODCH@wlFK}=pW=7dFBDTiH=yjCWvqkv># zVUw!mtnLiq2w|_CjW&tllQVP5ZEXt++5t=$`7Y3 z<-Onc;qog#_m4_HGH~~!-xl*X_d?iqELUp^OPcV(v?81>UbK?ui_2UMP%neo4tdB8 zZ@3Rh@{fD}`5;LWB=7iDzgvmCMZOxETddikx_Ryc%Nc&`ynXXG2MWxcIaUH8)ryni zovT*9Lfur=&a2xuw>j_{9I%luUb`H7-FZ5mDqrcI5`!)VjTbhH_V9GJ`u_DOcu>mt!Vk$lg7^D8ZK{|Khf^qD*jF+lD7LcjCrL?^(F&}&(bM+<0WxC zshvvGdGaU#vc@-9bJe(ewPEr<4><=xA-#Oa8R-YL(wN0t!$29K?&nrOVHuC)_BLo_2^SiJI2A zIhOR%Ku@w^O|KhLIn2qRYEfsg309k^9rHGxDB7^?6*MUu0R#+&0ZYh@z|fC$)Hz3UEk)KDXamgHN(-QU}$Iv4f&w!_lE3acL25iBm!vC^dS_u0l|2MbLRRTV z`UA`=kMOuLBCu>P*RD;IK4l>>>7z!u$HYnck*$=SB<)*V-IH%>V+CNkJlw#(KW0Rv zl?}B_l+?CHz6iSnuyxYn{szs;+ekx>WRjEmu?##T@0ocflpf7a9gmL=mKR?*hsT(i z)X5^QL;HH!=-IYWMn*@uuVvEq-G0ysXFoDHh*?nEI0t5-RaXfz%_OEy?CC3^+bWqs z``yJv&p!9s)B=22hp(9AAUp!ZUfR%+!$+fWzlM3vBKGHQj$P$sN|fTr*9;}#ECN;m zOmBUIi31x>XTET`965Tp3?Dtl*#vjd4vr$%;~aiO9i+z6^H6?h5){KZI~)(?vdsq4 z%3DI{4at5ZV+PLxc6jC~vAGCjtSSlc8xtJXO-RmXRTk^JWI_r10VU3GJq$jMb2-q{`7^O z$+zX=qug%NXp{v(yZP2qRauozjm?#3q$dT}pU`G-IMkIUabakNYv zKZ9%_X{6)kbNiz@!r4E`r^+|8vFn5!!g3#e@q+*X^ZZUbrmi~}D5D4nGip53swwf? zJL5xBzNPmqbfqMg+nbc@HHfbPUgPCo{PDp8bNSy0eJW#5#Xy@CR>kTsPU!6z-@F{q z=P!7I4v19PI3v=|^yT#fS7Z=&9T&rUqUx)yezX*&xwrxyp9!;&wY9yCknx zfcoR6zlM=g^CS&WNjg5Gv@Y9Ios8563DX)FeA471kf|O6Y+?1P$<)8KGv7^kH&iQd z(rRMvKJzX^p@Sd)>gBgU>sHq-(vX=(@l}(AQ7v>X`-QJ{@ir5Z8pgrj0V!ol1^B#4 zyh*bTDng!KP*5k{R@N{LnVh*=Hs@}XuUtHb2Dzu4I(bLAbmO3)TSSes|v(L8#J@aLgQ>$qu@7B%XEk2W->E4bLhz=pJGGXm34#=(%SLy#hG zltngp=#7(MZNr`T_%J{`bxZ*M^O_WxKrrdC4Vc%O4;4G!NBe9$2K>5gx}U#z&-b?fO^4aKQ`EH@M;n5dGgi_ zhO~}hdbErtx*78ZSZ$dHTrMr)i{1$pc~}RgYnb{0@VDr9Qel6^t2#+RRyOPd(2%+@ zv6?BNcrZW8Zdsz$r~XJXmtREZy+7^qa6>4NhhNvO&6F#ws_-MBW;x;X{G~}`fYlTH z$~%eb)UDDX5p>mLp5m220W*Q7nnu_xlh z$>Z#ySSpKbP(F?f+a_kGnY<8QO|=uDC1}a2OID@&;B$5N=v40#W;Jt|e2uV@b@xFq#`C?*D{fwNryFBp7 z_3~@K@V}J5{onor#-f2Tj~qMaQXULzqUxJir=ca7uOkpIZDh!alR4!*2TAeRlVF|R z8p_#^`;ZSGJ6->x*hMR zY|>8pwtHyPlfU<0B;ad0*;d4T zuan1~C$S?<`F0U%6EN}BB-aj-4$xN#XWC6TGe$=8q|zyAm=Y!AT>=p7#et%6(!(}a;T|`L%Fug-EKr=pw*ng4T!ck+utS{ z!6?soW}Nt#=02d5xSE3WQ+|)S*PY+Q!~_!&4q4*#OU-070m`H$acEoQ;lySgFrQT^ ztDAIJnaGf+0ZJQXaE-${P@8UlARv%Wozx93!xNNf+{h)O(Vn9j^_d^|Oj7w)jG$MKUVn0O#pklxmz7aB-|ugZVqoO0-e zro+7y0L5uQxy4+-)65_;H4PZQaPf;VX*qHHNL-(0Bl0@Dch$qP!(*#)679TckBzd) z8IzWoY0O`ynKY1K@4#$XyL`FKwyl-^^;tHnPL$rk!%Up;Uc&)LYP#ED7{*2EpSMWc z6Gpq>CC)3kw}AmFWr}jr$}@c{ypGw5-||kH_%!ux4zv?V6qcVlCI{@hFD)Bom|%6EVK6V#Ox3Hldhn!dm?G}>l+ zwq}L4mZA1>P{Es2egL)Rnp8#=Rp_(ToW!;3+WZ6MAOBDPb9vtfzN<=9~XIX6=8W>9d7t=oY zG{+|2KFt3-P;6^cFG9#TA5U23h&SlM>@?JcQ*Vhs6suB971#Q=2t3?(`TOQ)`vUxh zejFSyr$JV^-xbi;BJ;JL|Ai>O9r&Ar1BjEIpAACEO6N%fP+Q;=L4kdd9hh2e!$blJ zuU^%|Tk0Uq7oABjn#6RN46HyMT!PJ&6;58XQE>=&24b2kO4c?S;x2${LdheU&s&!OzZPIVvH@};(gaRCS)jn_Udj~nws)LdC zvf63qqSI#^8lbGETfqw9;HQK2+=ZvhGk^A{tRZ7F%Yb9B6Q@qH`Esnx@LTY(f`(I# zugmIlntK3b)QDZwcT8T>oTFq`(!>1BhUM|*3IGcr7g z+;?Kn{Y^fPMGJ|NT?6*teC+|wY=4=rUD@SG?Qe) zXSu89%wL*n04(94D;IpwL?e^H(AdZt6p*$PmQH|X4{dxAjk5H)3D1d(0AtY=1HxPa zOpX9*1s~S;5CkL-(pbj3Nh`C|cUC7TA6!T}-f4D}^6cT0AlnIpSE%JSr@T@IClk^v zefpx`!f(BoJRj7(A#_TscIFD4WkJab$ajM>;N1-ttt-4m-WJ(>sK5S;m!`43Z!70m zu^9qbA>`#S{hbq~8OpX~oweec|F$w`{ZO?lY&LW6D{mXmisR1P0LiMhsjxu$g@PeNS+Jo^VeS~EQ zw6(vWM24#Jtn3=Yn(~|aApLsri~oibz-w;RsxqFLT74gFGrWpP8g#o zqTP2OAJ0DX#q!k`pDBk>IGnxn1S=1(l>3(F%SiiLxpeU&W8V!{kPnrkOpu@c;%8Ap zVWu=Q%|wmKIupV1LwNCFl6iH0w%lMNx)c7p@4Bm;<`AP#fBLt}nNz2zhr@t%z&n$> zb1y$twmemqRlFru8&!yH(_S{{WBZvsZhyxQXCl+k{f+)zYoF?-f|Y^DhTONpeb8LF z-}|6>@y5On4wxhTb_DfLC8iNpf7O`=gzw5y4RrhOZ4P`b4j?aEj4tc=4HkIYFtU7Z zr$~q86288Xm9;u>7@;;0J6XFoWiJhifyigdXqH2Xr#4AQkWQJjYQs~jK}RRZaFAHI zUS`o2tXu)G(XqCnMe7?LD?@-Z(G~{cF!-u)!5;}>x{G~eskMQ3T~AtATot9qaD(BZuUNG zQfqw^rV@A4bAR#z?U?0E-2w`_$h(J*ehd5M?WHN~u{i(;U?@orh(in}-JtO6FFt|K zLI!YVfOhwS{Ce~` zAO}r+I|qWOvA(8uUogwy+{14*?SccrfNs$)O;Hx883agaa*_!wJW@t`(1fltxzJ^! zn&?&TwJ%=-Ft%%+!9I*-2H9l}ztN0gnaPD_JFW=a#j)&t#M3*;v16PD2`JL;Rd}HH znE9TCn9Zo+lYZ@JwPhD%I}tU1J(wI~@?)HKIELbtGoRDaJ@xE^G zlosu$n|7}`M%@Hzd^eLS8~v=Fd1~$g8Sbrz^gwaGu&9zl>^2BM{-Aq=9)-b;sWVNQB35}Z9c~?r{ zqmwBo&FyGrrEkUX@MUF-eILlhgp&o@K^v=IC;Bk4nSGWO&m4A^4(x-IDTGfj0m@T&WYI!u#%J>r5F+4KFIH7E1UqDLEV*)z~ za|JV?pYTqa%%YAVyeCZdFA;{I2`e4Z$n)(qVurT!$Y1+N`49j0SId__^ZVt!ANn{x z&Z&f$3z>!yNt3$Pc3o)*O{8so;R!!aYz@?EKslvGS+QPTdG6`*$_vlX58_)Md7U_U zx;*vdljYpAUoI2lqvgdXK9A4;CET~3D$A3X%QF4pCT*H&0sFc4e$brkw zK61rx8-ri5abuLJ+Va{l+Cs8Z-Ojawjfw~lE=w@(w zW-j+jEcFlQcN+Q}yd8-2I-}?bdKQ?(sngH%|rtRXjd*&5}5VDOC=;5BB4=} z3jx1;4gj&N@{E7FHWZ7(p45r)Do~N9m^^5T0bl)I1|VztX8(|bWXM+zHVyG^9)R04wvN-pR-?PHZ{=%l*iupj{IyLZ%!^3u<;IE-91aE@dR1 zz`ql^lqu_3(6~igkV~*sVacXIswnnU87DHnY3-VQQ67}-w8}y z|0!$ot3LLdep0@Xrlw(ow4)JRHM)A!!zAzc@nd$@WfCAbdF@74Y2;UPfY>|}SSK}y z+4wnj2yZSZV%paA!Omoe3F(qtK=Xg*#7Wv72Npr=!raAj813!?6KXgGu;f(J4Lrl# z|0t^qLu{y&7K}OIgCZDYJE&Hg3K7 zn|A28S7cF!5HB{`@S?fQ!FU2LCtup0YrbSVU!k(z|NS2>|M#!_^K$Wpr^;OqKUSva zkrVjoBqH=$2L36pGTjMu2S)$B*y=5@_n{NHv+qECMZ4`N7oUHwJoCh#<9Q0+sk|Ux z5953Ifd|WTkAJcB!k6)0>brwU`8-OeIsD9Peqz7E$JGv>@nKd{bmvLg zXop)%Gi8i^Qn7Nz;#AH2VIUvqrs$d$Ir*_)^d#dR`mwj#gH{9*}+Z<>(fV@# zU|72j-epim_!{x^x0^WgfHZubbbLhT9dj1mdmMbaSJI&Wp|7L^q}OR@(AR#sjbq#= zpZb$B^+Hb>o;X&9?|iTfvNWj6y;YWWUGWXQ#PzEA_}8mGl#GB`HK*L}bnx{qbQyZz z)g-a+;a}`Wc#?EpZ(^-NSPK4ET5p|alVbC2s9L%veAWI4lUgOtizbIAly9v?U7qza z5Ou7hnZ5CHdFk2nOpIQDZPYq8scy*B&Nbkefle&349L>wHFda4h7^b%rhXNVLpszNt#Swz$aLWv_Q`g8h8O%1KKwLtAa{x zcL&%grA@fz4+8m3yt(MNyr;E^vl}g8Ph8e5M>8*I)l@-Exh@W6i|LMWAkiS3LM@A0 zX$L$vhGx?ugSH^3yN`{iEblj(;KoJYGBmftlTHM;50k9rmfigIsT=s8UP1%Lgo44p z3w!ZFmcj*4?zvdT>_zv7t{5!gm)*&S=)ldgBg4m&C*y3Ezt;gv7cq00oV-!)y6X%; z1#KPiCXOB|S1w&H9;h^cDT?W>v2oQ6k^1b+2-BPiPEmFBV3x_$RSwbV z)B6bCn&!#32OczX9KefEIC+rwf?Mob0GMHoRs(f>43A^5q0+0L{8ucJ~CV zA$OWcX=W6FOkx4&f)vuAvgkw9!qTsq56L(=ShGHnJ7sT)`f{V4GULWvY4Q*&%POq` zTkXA#?1oAKxtG{e0rESbRDcH$W0%jHI$3dJyXHigBJs?!I3bmmtZER)zRNu|nu*B= zCkFE06-EQ9LH9ze`OSaQ6U^DB*U{Y1VInvH-!y-4a;KTn03Naihen~D(`)hUa^%Qi z%q1o$^BnatTbAY)%cKbdTDt*`o3wX*NUqSAc4IoWy6OhZHo!f;)+zfGcIcD%_+MXt zK4$`SY;=|LUz$QYd@lzwjh3-4+8k{_eBz`|+62bU{sQ|8HR~3ot_E<&e|blh04=?- z?7p`Lg99nPJhAHQZ-S;y{1(x>x?*^u8G|Y!4_AY@S5}mnb)H#w?e0sUeZKz(KU)6P zzxXHRC)xipe*A3QwL1Bhn55*l{3TJk7$$@B<mxo4lw zDw=y&I@>%emMmFD7{lp7|Cv%g^>2T(eBXzE5ZOZUuHgNB~7al?mlkK)y{}UCgeilxWF30pC&=EU;)hmweoN z)!)>M4fp`B0|Vw%djvm%4z-G)|1Fcm`|(nkZ(p}L@VYsGyr>b%gs*k4S%I3bXl>|3 zGN95C&{wr&jfP^cl1m+f5$#`VXX;}ItjMIVTEbbE;qx-@wO=3M z*07rpw~U#IhsbEhs{MI;n`R3vs{k&GGMw9Gu~tXc7jT0n>~PVjd2c*kCUzgDP{xk&y}Hd_%t_zCT*MzUAQ3i;GPxY z4Pxu=N%)oPB7_EAPQ)iP1K`^<5GDJ=mr80+uS#mn%S2@e1I z0pQvxyL7G>2<2#j{GFW7a&oIcIx7lH5_;h0HWPyFb|(OU3DS&*6?m&@k+LRe)((6} z6D-61&vWzcgl`>8C=?D`l=Y+rbeq1&{>T*sCr{EL=xReQ)P&0?NQ#^YT%=Xtt=#xr zd2_Wu7mLy&8ro#ywX}4DglhgMAksNPJxPn&W-2gzO^?MS&@!a~Aydk` z^l8`Mrrg52*v*rsJ&QD$W`KKW%@f`sn`zo1lX?!EpUtPL`@nswZ;Sx9#bE> zN7SrpX&&40_Br-7^fS5BGY)lX4pfrnG-K@H5y-k3yAJ^GSGbng|FOD+c^K{N z%49VG8X4xaQuzf5eN6l=O-@5qd%5Gx2`1rJ%JZMc*0~L{p>{kS^|4>$^!-e#Xj2pg z)k;~REbb%dg9jVbb#>jDUe{`;cui+5AMNWjGlKtAaE0-FL$7T1+YnyzM>)3sZKFvET1OsW1zGccMN@ z+kg8zA1zNk@l<*E?GFRAoh)>)k7_u|2UtXY$TxDSyt?AFj$%WTFg5(E^y7Lm)0EIv znqCak*Ld!}2TP=R*8+8>_Zgxq(mdzm&0Fk}?hwzH)MtX~GcO)AB{E!^N?yGAKL-fR z6`ox}_fc&bBD|C_C4KXR-%jD1hy(U{^r$XPEB^7c!kw}dfXz*`6Udki#ZZ1~quCwI zcvN5HhCfZKYPGz$d1_*t09WlgtNfau8a{K0)|~g+2WzW4fVNe;Vl_<5*RGZ=G#Tvx zv^9>X9zJ=t3{9Lw%Ya4=HPi}e)IrXUUM*9dJeXw$D|7G%X$eB=4}R}lGNgi$6o_)v zaubRF^}Ysf+FgE`rk4QO_%)L}${%~tCU)NKzS?^i()Kmvnx0`C(wkJ`nv68VLq`<` z7wtsb(KcIemQDQ|FqVjP_yC)3mOBMb4l122XKw&drYFyHGUp`>k^xp|le@{F9kABN z((620w@qv^)jq3Tj08ZNHpF_d(AK#};OvOZa|Z_}ax3ITCGLPEAa1}<1M)WT!`;Jx zqo!6s?EyW;SAzr!f&lxwmIl1l@d-e(WX^N7NMSd8bU;^2+tCR?gvMoD>UqVsnNV38 z1~jShj41%+bH&E|may$~6XJ&a5{U3E*ml|9rFS=*3PNY_kYE?Prtfh`)&6vt4P3@? zP}Uqrpe~5(iZ_?)PhEGQnz6{I70iM3=u#&~@X0nu)v6utB=PH0QyIYBBs;%2U*=do z*HXe0D{W=kU8|YDfmsVAl*b-@h`gCFIJsgnq8EogwaMhQP2G+I1k@NVT%!&FWIbri z2LbiRkKSFT$V)jGz;@qBt%JR*3T`mW#z)Gk3&{>ajda4RZt{@{YV$1j26CZhet`{~ z%B5}B4c*F!zg^8xBkF`m8LeQI0`p(jZf>a4r5uuoNyus@e6@X2W_a)FtB330002M$ zNkl8l=qb{{!&}*Bv1{eT2(gyr={AqtyuZMw|#`4 zqyx~ZatRxF7SkVTHSM%zo zJe-@u?>ehfnl@hE)|} z3S2Fd5Yu#Jm0%k-Tj{()CMY*w)DzEX0P;dvX`|7q9AIifJbN~t5wVRn-$9@2#?Cf2 zU3zGgtEu*YCS;$Y-}e>T)Y&t4l#hSx2g;{D_1opge)4bTwARIW`zf+VzB{s%OFM@* zM~|P3M<7?)oZzp_UMMvGMY*haW1JFJ1zSI}sy)`nN&)%XWQUQpS^~h|8Xj z=bwMJeAoNmmksAztn#?(;pTSbOMYiFJ#}XpExT>Ywqj{ES)tNAM)_S};;TuX?>r+x z*%@F$@5EG_eaA>WB)!$Hj3>we6&1Pgu95fM8{XL#4)8iSVD6Z)hiK|$=W4)#m2LU* z*?D{W<~9euHV4!eIiq)g%Q7~j$uE^3{qdh-07AQiZ{)eDOK8Yn0i4e;sBkzGJf2tUq3>*O-kf^DN+GG7QcLU~Q0+0a(4ZK=&nD0QT z{cQ#33@j|6n_f1B!BM?ZD0ikKRuTjnZxwsoY*3UBf*HyvcLZAAIS{VqKp{635|K1w zijaHQ#(?Q_>6PIzixKdVw@cK~=u<}{NWwn5r1bTq_a+%NLk)* z6IWgvRB$EGQbsL5T}SO#eASX&+g`N6{Lac->cCD2UEZzmw(6+CiM0TH$A@GqWgh!f3mi*GVio@!TZ|6sWwDWFpp z@*n;dJpAB;Wqg>` zF48~$r9UpqPd`;2`nK=k;G@%c<05bR>Z3=FA?w(uFLWdS!)1wrrA(AgzQ^Rn`xKmJ zO_}9e%8o2=>KRAj19*FIQzkOZ%seE_y#nie3;0#UkRaxhfO|B}?kgX}%w_|>>>E=r zVZuaQwBP4mLE8-z$J>_5r~Z$BUcTeIK7jUlsq}6gqrI@I1|Ry^%hX0Z&j@JeV5Z)! z#j-wgu`J^E{qB=vaXxkDVrB$B_5LA$$#++npS2U=i6pa%-HV zqP+6o0Z2fU!Q7ZUVEhSer~8VQAi#{y)Md_XfT9+;tbOC|i;fqqLnmG~HfJWwi_=%i zx#tJVsWbPKu_JgB;AF|QEjBm_Hth0^Z&^|{J5UMBP|zY&itCj@m|H{C0lCsG{qn=h z=T$47pF4ki-vEE(dud8yEVDF?oy!1u;Y**IX6x2hrsd|7nLT+5*X;#&pOoxSgonrT;r_)96iKEjk5CMDTo3l z!Ml0srNHopktu@r^kt-_{j=kj1DAkI%~{Mjpttf%PbF+&W3u2uK$R<1D@d#sO$>b^ z8`853P5=e!9%iJCs{X#E+fA8>7Jy;}&2l}dbO>)BH*VZO9@p^2-d~QLxs&n(zzDA< zR3fT51MbjpeYUl(Z8;`AegKgI*#S&Wlx2^aUjPJ+jEqEcy97AaZD?mFWdPWBBHP*w zZ*?&F6!cQZPO~=2TmR?}K5)8>lGdiDe*)6gSi2I^)62w~dN40nI@UKC*r_K^>AiFb zPY#&j%wyBOJvB#K)0D+k2k3@=<2WJnv|c*`m)A`z(iVAd$)x~E<`_&_P!6mo!|>lf z)|snrPN+RaGm|dTbd^QZp}4E09?hLZ$c~9u=&Zd+G|7rHm*ukzYP((LS7WZu)Zc2$ z``Od5;s#1+N_!+u?Mvtz;2pWQV z8%Ez-q@`WGD=t}`qa3oOGNFQH^VV}v?~wlR0Z&ZC%u_m}y-#|2hhc)tiqF~#rVJJ~ zZQgur$JBg4DUT!n$~0h;eH%b@o{=x#t|ZAlCx^E0c*Eh{3bF=ABImXnnk1l4Z$yH1 zHaD{3GRVYE&qA74jg4WaK7@%NlU%jpu99t`xt#+Xwqa>R6K3Nio3Gh8`r=EM%LIPW zhXDD4ZW>Ja${+o^as(5R@nd(CbvN#xKFhv?>+viz1c=w|rV|M__14p$Eu$35Is1>+ zJ>OeQ;F(|8r1`)%jqUk(5Relbi#lcR_5_<&nn=x>#Rauy@U+% z9MHT8@$0mKZqohM`%ahh=g*f<|N1YMdmefaFDG9?jtRpQY!D@nW>3>oldPt7l$QbJ zPKKPIdDs|@n-vmQY_JI@O+5rTi6NuNsQsjq`wf8dU3cGA9{<7@$alHib@mLUh8w+6`I z%7b{)pyRc}R->d>7)=XS7?5HYyDn5=K(+H`AQB)AXc|FhclEg^%OzY{9XWGv89jcc z^bF{=0&fa*r0awgOsk!!^wHcyuQ@rm*TJDhZfFbW@Il&Mg4aLL`L)`b#mxIw0mHK- z+%UiZ3DtU9pZh3L%#U(~L#;HL?nzf^bkFrY;T@FaPfrH}J)18J2Zd|^zSq!Z%}r01 zGj9AFWW&?sB|7!0Wu8OZ$j}b3ga-hK;lV@Eyey!3oMxk#Ky_5#$goCW60IC{5_faZ zCOFpCGewB0uar92VBN{+A?^{VZ=m@WR7hhtATf5`fGjo38)&lRQOp)BzLTiLvuFYg z(?pxffD|(qWt3-b-s{FIg)9=}co}bE+VC+AU?s zyflLuVC7&M&6Z%#6DHOApEz=&Ora&5W0`-GbTk7JOsfUej$M!-{+VRcr=WU>&39^| zH3jM*to*Xv(}3ke_^zJ8{9~DcT0RMm)ws67qnR1B>X^h!c2=GfF-s3e!Bq_*(Hp>&8f;@EoT$~gz=y!iW>QH)iCENkhblm1Ffa2C9a;t zhMkHa8`6UoXtWvqNCpc}Bd^X;Twwjft zUiMYM3%#BA(flO)1Rx|6ZhK@p5?U&Su}`qx9CuvZqAvK|k8Ji}>%ETBVW0zZ8&1Yp zUcOe=@pr$9`^OFT$!wsASjAI~ecvelzdHt~tFC_9l@mDZveDf4GYRgZOz-*D$IPU> z{NnTF-uoZSI3f-6oL;6d2_w70V-1CZ?`V3j>E^R%Hw+O=dtdHCqWL1lvVQrnL5 zTPKfBcpiDjJIYgEdXfpuP&sn+Xk^$!qo^fJRPfN_{sj6VC&yGD{VJ2Cku(ay}y0k=D_Rb0OHbt$wK^z+d~FrDMMAjX!igYI-Y^SM!00oU8w9=4*d{J-qI*^_12b)R=~xH-@BRC85V&j0}e zBuL1zNn0UDSc*`D-~0#qMS8Kq5e|z%nxII4q(G49ZZyzc)iqbn)6JWiH)ns}Z{J&0 z-GC$#oewOaPIp$`JogNHSbMGC8aD$QOC1xWl_wTbzOzb*Ry%>Lg#!dnCM6oPh>V2! zGwQJl@Yyk=ojxbC!{_iE+Nc%G814W!o@4B`fJ7kc96H}xQctlQwuefKkNx&Ok)2AG zz^pt|nknxSTyrUX2GsGMCiCxMdjxg1$9od{&iBTE_Yr0zP!psGo~)vhkP$5P;Vu4sl#8xMc%-zeTY?bAJDH$TB~_totaN7sdUz?t^khmL1lra${%rYKHA@oO1lQDR7sQvlF^}8EZ&r^V zKcU_9WnefVF(&I;N>U4{U_2p1juUfbe0kQoT9q@*oJf9jB^GMWGRy%2j-lh=9OOLf zF$tUerP5{GeBAW*gR!!9d+J5=y#7(V8mwup$bP*`_{1};kfT(maNY_C7Q6$tL5d|e z!S<-gpkVMk6)GMT5ajxDE=I7RZ*A%+p#D5y%<3hsmrnZWZlNk)CjYJxzGK@B%W@2) zL#^ZCQRyIU=XX&QXg;4@b|ryT`iBOw5IY9lC^%7W>4z3|E%_=kUh6x|?T}ZgSQhZ+ zdu*|z>Q0{f)M(!-;YGaT(C4xq9Mdw`&H-c03F+K%op6&G?q5fV6v!Ei?9-$TL81I1 z)_2auqr-0v8Y-o(?Oa1EY?n$$2ejLxV#GUKbNlR&Ti;#huCxNL5}>%BydJI{Wanl< zzH|+{rM)B-PYgh>&1#d;#6R)jzD94wkAYhB|o;k15X|g~^?CHLLZ9A60WxShElC29e9L4_`Wb{-o+=oeE6P zq3as+Ut?Vu!ZrX9R`GSW%^u#T(rUiRF?|&X_drX(^Sj@w_Wtt^tH+O?R99}^tPYhc zSigI@N9(m-7uTU}b}00YX!RGAJByz5cYCdJg1&ja1=gXdUM$}4e(+xPqrd#?>RZ1{ zB0QkGLHL`1{DOHh8JE^Gwu~`A-p5K`=J4P{@rfaW$UDYGI><%-E6v%iob&wab+`&% zJzO7OJzk!9`RS|I{8RtFL|~2(p<4G;#hb#)}B`NRa~xY*{rRzkMM~B z`gewPCqjs4bkR?h61@+nJWL^_cv+mDKoB>vK2ouP(2LWvL&7Yub_osv!d78XkZbu% z95T;xPRsnrm#~^~UkAcklg~NVndp~Y0d2XAD_}uacrzfz>LuPEp-i9rY^4bQb|4jm z(uVKVDhUx}w>+)O0XbN+0t&fMP$anb04C;s|J6e5Q>5QM!aM{@DwfvZw!ByrJb=a+ zAtg6%T(4G_*Qy`>@JCcxm<8BQGDdhFXwDY3oTV|X_9_8d5Jv!cTJM?&?*`!^y_5&+ zBvu|RCS~YO009%@?EDGR*(cuog!c*Wtk15;K=9?f^b$=+bN+t2kZ*jKhmEKoNiuUL?NJE35%&EG1E{S?{)6#Y*6%U zbu)3eE6eMIBoMRDI|m7)$e60&FmQ(6u*7W^V^->RH{t=H(g)+EH!GHD$giIm{XVVF zTx?i}^VJXdn;J)9pjXD#y8XCi{I&sY<`rDf$M1amcbK1JVws;pZx_`TrThNy+uz7F z&j`C2F+q{GSSE6fI`Bi(j2AphC)bx2lC0>)JC-`sLastkXEPm32%0D;zY_Edc20Po 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

LVVHVCR611#WRA{*T6xmrP=o{R>OCX9aS;$@ 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 literal 0 HcmV?d00001 diff --git a/blog/images/20230722-receipts.png b/blog/images/20230722-receipts.png new file mode 100644 index 0000000000000000000000000000000000000000..536216e3d0f6f50420c301a52525283b30b61044 GIT binary patch literal 485556 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 Z5!kg)1ED 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 z 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�qTiIKlJIaxSYb3MU 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 z zaQbo5;ellG7cW%_#zvlgK(9$DQ%w^W;s`MN`4m-2~ z70*l8{svGUXZiOQ0}A;c|K#TgB97T+Ly7JPDwt9eDkooBKxK|lAA;E5nW3XJxtZ+z zx${`S`q{;Px4Opiu1nM|&rRZ*vVq0vOO`-o{0i#ihoLCgG&@?Zp$bQ^!t@}Fjo}y=?IW-^ZVcM4hpkHDa9)1dzBB< z459FAA$`WG!$p?rRZu@+Xa3C__o~i*tYfrs5BI_eRw%TzzW%K@6RY}1Kl*94OBbDe z{W7rPhQhlx!or<$aza1PvKOF>13UUyInZCZ?<6PXp`3YIsS#=h@%?wTpvLdRd-pii z^hwn`jKYer9p**^vkd-3UYlt5zZ1HtbK^i@Tg?;pj3_y5Zwa{?Hq@7=`!==_I>b=O2WjawlW$8 zW*AzT#T`cza?HF2w#<`*fixSE_G`iCK0 zhp?hM!BP1cXJx`z;P%loHn2NRjHt50uj{g{oSZZLEeZ-S7sm(HJ0)T1HBu1faJ zyLb6Lnv;oVfBF&q4^2ZOT9uvn=9Eg5a1Y2)X@)Ic7Wz&CeY*nE&FaD;u7+#y!FBq5){VTtNLKm;_n*O0_;(-saC&h6>&*TLd8DU} z3daC_+5s(gvC8)OwOi1_myBilh<8SyakbR0b&NV59g7Tr;HV9mq*XneR~s@38wK@3 z`F?_PCZ)8!e+VAH%GNYS3Mc!@6^#!n%aBj_iiQnof}l6J&ftQFe9`@dW-yLDR0*aE z3{L!Xg_0lJr-3Osiv?V~$RngT$GX7+S}a7~0$g6c*cx%zJ&QX?iy{#7d<8(lk9og z-?oiMrkD$eeF$&LC4tpo+F5mfJa0rvcl`Gl1AjdRa3z5e=44BlgO}t~tiLKF+{4xN zyLI!<9u~QAgzP*D@yg0#82IwkT{aWW!YE{j9`?}B#rWE34AZ{W2EhmISJFH{&Q!~#ERI)W}s^~ zrVx?{U))#*QJmznDmz*VRi4kDos6$!C(5dDssJW>4N9Mfvgl)6EYbK&GD%VKmZ8^{ z&UOT*!&!K==tNN3W`66_F1!Z_3N5Q`2&XIWaH{5+^V#fXFR9qN=~N|40fK$3y7cZh z!51u|!6PhQtC{)b6(<#b)ar?!dnk6=8jY?J7a@f93W-+qccr~{e8=!ab$RS$Vk(QL z`z-Oh$#Wd#Tm?MVWoZS$ZOm$AhRs8ke@%i*F{JrnbLzB z?>#wCrE(NSqts1Xc;3s#T2}zJad(RHk5a7vurY)QZ%%Po|M_P>?67&xh!{fR1k8z} zN}_$)(!81|C@R6WsR`xY&5y+w+5<{-tzwL!nCm+nH!s>Ol*apZGUEhSoZEj|gx$Cs zIl#MaXdz)Jp&|1~Ylh0C)mcgCMk(xoW(>LMYL5p5J$m#k6Jjm6ZanqW!|?$&iqijD zsymp}wXAXI5SC#hP#8AU=q>uyYi)O9p305=qWggxgYDZBSfLjdpTGx-AN2|+j;^lX ztA@sbpXe16_&@iwtf07VuyOS^IACnz8||dx@zaO+&CkTL-D_kL+S5YY1;OSQ&$Z5gg#CnBoW@b9Hr}f{8Mt@0|SISmt+%TRr{TNF*{pQjM~09Zh$ zzYdhkx=~51PCnaNZE;nJ1sCAo=2YLIs&jv->L<^Ii<9s&CW}~!RifSZbIb6tQ&XX* z^nG}wlO~kR?*7obb+}2s9oozop))pE@wALx?mL-$|L&{bt+v3sAyS3o_U+rWcRg-l zXD?ANG_o)KLE}&F-mC6Bnq@@}IwQXaZRsv?@6l7*2VH|ZjV8-j!MEY{@}WoV)6fD;~kh+)>uITUoX=YXoa}}w=^6b z)<)!I!K;RYqr8qxF;`wmK*cKXRS@fjL?)#@N85<6)^_~s>xO|P{I4Ig*4)Q3YW(^T zkt(4jBw{N}svEym1UHsktwf_(eLO@&=CXhw=thu@;q&WG|12BAK)fG=fpp`B;K3)- zt%Arlr6mY4W7Z8Y1SuLh90ni)$muZy!BR&)V8lnd~xrJ z5~;E<&C-4w@uEljMtELev`!TzCmhQta#}TW0sn!**>tv(HnNJ_5G*XwB22RM3f@#G zb*oTu&_zN)v%ILa9-+_5gzuYyTY5SlLWE4zL;x0TwtWYEp5$`C^t{FzVX zRmGI1#XU*E)`jo6iifZ$)T9+xPu7V@Z6H@Mmtj=4$3@0UOO zITLJ@S)xx&;P2jvE7ke_(@{<{*65Qr#x79a2v&C8$JUsf8WIsa&<~9IjJXv~xNOHl z`jk3O^s)j$WQ)hQKdIh*>lIu=)~lcX{25U!ZWF5-%RilI^olN&e3eAij3=z@sO(6i zY9NpSHAB@3IL5OR7cHEwAh;S+CSio{#FyHGwmk9DKGPLK%WM-XA5JK>$UB=_X7cF@ ziAvo9e)C#C-L%-z*^e@bG7Vj>;)c?VMc2!C{_`lG@)O;%RFL*jTvaBeO=&KQ7h}cu z#({ToBmKn38tA1zebJ#*n!#_bZ6*z;PZ1CO-eYKLrTYHcH}=YvZD0HPRt{~zce1I+;ZBKm+h2@su8= zsnij913;3ivLf_C|2RPxPUYdi0P>P?EI%_~fG#v=SlP6oM@)Q{a4D7F*#`6JA|_4Q zMlaiAokri1Z(p6d!{%7!m^Hjy*CFo2*^BU}DUJ3x5exQ?rfo;2mAj&8o^CRC1pMe1C}rR z8--y93*zd=DlRzF)lHPwHqz?LtFq#OPpkS712a{lH2JLfi>nw1r~100l&MTAfL#S~ z>D@`5Au~-U%r2QLNcD~OFeM`_7y@!tA6?3qcC_BBh-u|g8P$TnPWo<^=-aUbcW@9^ zZ+B~T_QFZvp}z@t*t^u4&8>UQYzoBZ{=~_F>eA(tD9}VO;eaVRIJmgW0VgWguI@y6 zq2E+)o0@tWv{Yy!$^dQf;3=(fhFGgt8nis?|L;VW?#MWk{&g)7-&%HiPzdHxuAOk` zLZMQwHQ0%V?j(+tKFV|>>=OG07O)VzI%Fgr%W5Z@j^%IDmfbZcLr$W~nCU|4RlwbG^{pJB*(xv&e%%5*w|FZg9fAGu+xms zJvg^od6ad#I;6sC9oaC68QE_ShjOxCvANa>O;(I3%cJI>&Jb&x@e^E%Q_5N2@%#ob zL3=x}R-YK}OPRu&)go{!ymUMJU!YG{kI3ssMf(Jif*6iWybaT8y{>NPr>;Ag69^Sw z`W1K$OXIFX7un{+wa&H4kuYg=>pBc zv8QaVVL@JNVy}e6;|tob>^R}$#6uXIOp60iEuJ=sl(fhsLVgkc$UEK6yn=VW-9T>! z;odPi4QZs)$N?twmQ(PHx-DM@z_KD*;NW%K8Kq|C`2kHlKjIt$T_Ih$lde?QEmK@1 z8W?SGa#Q|OhxPJa91UW$Df@1C{}Nr&RmK*0_@ygN@sDX5ZU3c2zu5!n?dUFPE0?YK z6)h=k2%$C-9)BHU;IGF3Ok?uW#cGh4wlcXeR))VyP-v_%&i#UKhXHT#ZUu{y!qKp8 zD(eae(;$jqkdnR1ER42cO^}KBR;FdjFsr12qsx2_C@)L5wdK1oq(gxyxsy(dZyC(t zjd|7(pLwp@tp!P89_utrStg05mXK#I2^vkos}JSJWovzyDH;anW;&L;QGRsi&|;~8 zmf>xq?C1}CV&WV=t5}$@xXj`sYYcSbglqlW(LxSBoy=%8QVDfgvIq_~gaKg$l2%IU zvTX`K(|ZWbHb<*tcjeg*O0h7wS;>A;iAG6hDcQEmsP$)WshK$6#tK%bsSv49sK~bB z+brIVAmgOOwkUkH;&>>BZb^1aM<**9^r8Kuvgk%Aw}{Qpuo(_TtQ&>d4Q9Go7=hu| zjk}4)uz-NlZ`I{q`#=k>7U0ut5bMY9)B|q}ZHU1X3+zNSqJXC#RWR)*g_VLujJ`+oO$%cYlCna=f1g^fpKHo#3unkVwN{BX=%)yg56lB}t=Gqlk5y-2Xjly3) z`vsmmlA;U<>nxjbiJBoUwM4ong2(8h^ey9HkBy94ZM8g_hvrZR^#ccnnV4Zi zq^l-9V?))whYvaE=TQ`sveB7-!_xfe=hrFUNG7%yr2+*v3TR-qO<2u!gP?tG*p&8G z_PVMc{>A@Oz4PYz>hsV3ig?{s^@rd4JvNX30ao4F>J}%Ft}vkszE!e-t<`84^rbvP zyzL8^<-q1SWgp#RIhr;U}#%ZPp4CGy}ej zfF!=fl~?-EiiL{j-g)mtn$DvxKG11-p)%G|jWNNNCZ$zjcQdk<|7J8W_s~RvB)_!c z=jM&uF#xoppx>XJtKRt5JJlGIF=+&9&HfW=1YSOti*hP|(8b0+w~5l1eQQLV4?nvO zt;&Bmga>+XC5G2gWG%512kW$p`>ejei*#*r(&mbdV^PD5eP=rBwk}->w3_?QcFDKo zPfPF_7C?c8HcC)T4&?F1rZ+DS%LIdQ5S&9d;y|?O7w<|zWQc@GnKnmX@ZgwV@_Xxb zay!q2$9i-<(n@Tb#ff9oPIvW31%3nNznc}1UffP}Y1AS=18z;`o+3cXxOMV7K8Bw@ zZj|yp-GH{BU-96|lZFt?f1r%JsvMOCy*9&0npdutH~ft7xCr71r2so%FR z+4W0sKYt)iAk2@nL1ly=MUm8nSS629Fi){Lc5>$HBtx>@*7 zMg$Lvg*9#tq;<*AGG&I6v`iS551c-;bT1Sq6uq*XsP&f+XD~a{C^)npn_r<0A0@fW zj<*3$#?qtmn*KRp z6C9na;?s+Nuzjxu&BJ0;V8u^~grd+bzV`j?#9D7y$UC=OUf1$gXwCG^(5y5lH7LZI zo1{ryQ;JJeXp(od8b#H7DrK8kn3;G{_Y)LDCmsX%Dyy7m=@Ss~t;@#=;*RU?l4_+r z%J3|f;x}xqf}_>SduRzd5y$>AGLU|w-mBc$SHi5qqW`dPdzIyC%V4bJvOT4Z=5cH! zEd@D#>>JzaCH=K&alohA)@Om2}2k^e=yxhKq`Y~j{y7%%=IYy=nE9Re->atnw;yIzrY-m@_^@R>z|vk;y~#R{4<1aeaw1 z+fe%4UzXz&)A5TuQKQJ8Hu>JNz@@8Zjw9=}f5db64f!oYrB0gP1#eCtbS3j1B&D7x z%H%1rl4;t(No%Vb*Z1IpN9Y`0MFYj3=XA%N8;6v$S6(?Kk(Dtx(C z#%!y21)#v26%b1Fy>1rL$pDe7>h`NRk~C%U75KpfcQ%GqA{2Q$&r>$3GBly`Ov04m zMBg&xS1Omj$mEmnOaz&v3qv-*W&&;aQ77GYk@Uz#K6847{`nzqa!h+UX_C)rOoY{mRLnq>F0{JR%`-(?Y3@D~%O~(q7x# zN?YQB2h5R4$QJ9fZB8UsSoP_GU%Cgvs~E*f+k`!3&<{?Cy$T*(-7xOFu2+-+ob5)| zN{W}~PpE@7dNsbD1OM-^F-NAD+a-+W5TTuw%P)a%{QSy&Npbx57z2MJ22`vf=%FPe z52%#Z)*}Uh{&X@eDH;Dobn}hMg!j_9ex9DZDAQL->w!6|V5%q>?L?*);)l=bN+F|H zIo5?FOi#vb^{fPJQI<@tho$5lmYSbU&mxF890+Aa1-D7-1Pt5dR+X$Bmx5X1Gz68F z@D;2=ZrW3r&tf_5LXnd>tGxBdOkuv3ub|b%K%pc9@8U>t1zVJI#;AC4iB#oP0jFS; zxhmW&%er&=BUa~amQ>yJXIoXE+r%&TU4$@mLL;vF@%OKBrlO^$Qp!OPa4*x>JtgiO z2#21&vhmM8Z9^Cjaq^*^=VWHE1(y$`iTITsw5Te4qu{Cq!CVvw{Li;-2_NEj@nv_y z<^)P#)+H?L3zYX2?t!iD*E{P9AC;6xIgYG%5*7w;|QG@ng*IrFHPsZ13G zx`jR{k#vm7Z`++lvE7=%Kh(ji_vb+Cy%!fqR!#HoNbcbkX* zRBD+;$tjAt);M2g0?HlpsQk9yCAYv_dLj4XxhSDbilA@Dp<}C?3GN5qeTR-7s``eT zs{iY!KdU~Ox`x|KPyFNem=wF>CLK~-a8>Xnjrp5$L73$O{vobve26ajPeGfwkPi6f zLHeJv3#}G<@tNa8JeIZ;MblL%`&2xrS;ZZ}cJfa=IyO}3$c7@!q`0AUIy^F-QL-OF zCv-QwP|GZJ05GqCqxc7>Zs;m2Shk5WG&k&^B-$`TOSoXo+IP*+qTO`b-{3@Cpaij1kSqO%)?KM+qhP={bnWVp1H)9C;u|2lcd&gPQrO(R7)J*Wx z9{3CJI1y9!x)LDFmTkTAn5>RMuL+bu|4F0%d)cl6apUhVf)8Hp4{0zrN7%k2Q_Mv; zt04uxVBbL)+BU0>i%sfIBrAx+#Btal+J!<&F!b}%%r&uR;cvCYmlCemulqeKxi`N#`Xe|pW zhca@ld@>?8z{tc52iFWkOymlLl77}&VYR-t!{k>NvAnXX?Xs*Jz*J@xB7Rg*-SDKK zGF+Vk*;N8JAgz*q6n{?Jr!|%N0kMC3Nsm%2(B+{zL>R(yt83l_1k3??jf z%x%}pUMqoTPf*Zm3g39I_@b}{L&-8mtZvLbL$ zI->AWw{24)Fw~>UW8emsR>CQo&jvPIY3}(;+w42*RoDxzY5W&jG$p`ngJ*LUoJkjj zN%pysi}IpSBP2yb+6ZjY|040Aoyf#$ojS!LG@+JC5ycW->Wf-~VlQ3mIzcKFxMJZK z$Ko`7;qC&`Xq9skY}nDzfG{)pl;-^HN=r* z6j_(*gA>z~1IVT^rnGpl4p)kLSc$RS%S2kTouxd-1G)J~LkvwW5|Ixy9ny4p z14?Ko|5LX2L`5WeN=G$}i%d&15iM}>YVN^hiZQUt25Q}3oM3>N(DF9!P{Ja5%lIv0 zz&#Zyn{;A3$QLf!APi;_t{PsIzT7wPE>QmSJGiun)KL^ECu>V6#}0>@HcW3_)@#}= z#hhD)=6i~7{L7@5cspS@0iZW|K)VP8Sa6A;(qk?1Q z4F4GuObAF9SfF1=Tz%QzxF%7sQyyK0GG6Epd8~Ch8M6OXe9MGIZpDX*pZRP27z=$H zOLNNHD)cA+?dR+SSJRx3Xh_K_Xv!BUgv2CIHhD7EcZoC>gNbs5_S;uZGQGq}VBlL? z^s;X6tBU1`(?TcHSia_ z2)u17*Do|A@9THJeG89NF%MV`*o3p(N__r!0q7TQfBPmJm+ElO<;@KoMv# zg<{D-$fM9{{Ze7rT-rooMlnTtX}NF^?Xnaov+$q7KtU!`g>As7^o4{pVN7c*o9}dJ zg-{_dv|&jfMh^YTKy|O!-!eZ6wsFKLUuLcXtlwQ>gGS| z_egSum@%|HrB6kpx4*L*85@k!7lF;QFmwtr44A&63Ir8H1*TC#WE6k}lSDAmCWTa# z5)?HiN0+E|!_eB( z&Jof8L?$wj25KRn7BK`q_{_=&f=`%Annt~JK!L%0W>siW4(+kM-baWDS6%=^V77fG zENNA!tw%f-97?ePB{)?1@n1XEWy>*=j?34D*IEbymX2}{fb``*>#sjfT5MjVuQybk zRUYu5Zses9cPZWc1$Gkg&atObo_cv#3+%v2KI=MsW*bEX-{Rk^3Hsnq5&ZgV`;dCQ z;?Co}@bJREcjIX4r{A0~Cu1^^p;4yT0rNh{Gy5x6W9zluc>`RQ&5yv&bARu%GNxkv zlOO$6b@|=*ssm%zGdYnKHaSS=Al76pthPZ=QO444aaNQa`$JGdx4>YX(kPD__eI$W z4+@C#mw=Kn7r04XRyDNF7yQQ3X}Qu7by{BfEqCT`w9IzWv_2wKX~?iWb$%SCORQDBrZvi4>4TxoqSr$@w9Fw9a~^q%KEX9A5Rk@CrM> z<;JO-`b2R0TKWKP`KRMUKAedf1^L`n79$H;U&gcfEDzYC>~b$}58b6s`$D%3CRyb1 zQ^RGyX-AMx`rLF`O%dn(OJAx;H{MDAJa$N@PgGFtFO4)R<3ZFgnSw#YXhKY0>#hBmyT{OoTbhvxwJaYL(k z5NF~`mzOBwv_aWck54B`2aI!o0PKR76iJ!#c;&5^bZb9QD8FbAt9Z6m*oC9Ur!d-Q zUU4;YRm(KCwL!Ug`4auHO}4=+_bjh;1D$Fc&x>NmQY03^Y)A=%(#q}fp-ZD$@lfVb z%H2e0NUc~fV2BE7nTgR4w6e;));M9%^v2cJQlOvnEUyGopquy3oIZ_^ zm4U&W7|b$e1-DFVaMX~D2uR+!1fO{5lq<8cg<*;frly;MLq=vVO!7mmz2ZTILoK>% z07i}3NZ;pOl&>6CfzXtBx*CvZ2$U=w`48cG#B%+S^#mBTLO=z^jh=-eBPi8GA|#mL@J-Pv zp9E=tHrR5nf)T8cRi2U(xQT+;DAQ*V)ZB;@tM}tCz~%z-as#|t>3}&f%0z{u;Kp~x zH5MMrW4d7G^jWzr`D%WKBT0m3TvSTR{8gdwpa1ho=7Q@yuzvc``ovlkBNE!r`hoh4 z>ho=ZQ6PEmBvJa=!}29<6+u|&D0wZ*GEr0_9Qo~kVK5>|Dj=cxB2;+ZqVm8M1vBtc zK4sWHmg)2Q$|M%pg)dj5oZvF}MkU>i*f!2SgWZ%xAw{?v3WUkD72^AJ-5N7OAnCAnF=BW((xO!sFTeI_>60 zI!PMg6fU3HKI*b8ap$wz_+uHu>nfi#A9+I^P7IV+b)So8%dTOty=CHW2`|r;-WL(M z8aE<6*l9dYBKhB8!p4i={Obr6^FrV?D0WP!=kS5F!ZTq%I{53-kH5dR7&u^OJ^t2M zN4oTPMnP0M$oxI({QxDy-S%#f^WdDgX+VlD?`;u9L`IP%KYq(R;101W=@Y!=h7_2f zmWrI32ctHPabbNfp?VllGjX!vJ=N*SQ&_%NS>{|0!;;B$c5)ge2bx%~4Bhy}*)#)_ z(jQaT6!XaZ6hXo-({nQ;3DTA_E?i=SDI#Qc(J)LeBX-CbdshWX#j!9}D&oKVz>T3Y zJ^xxxEFjiRTJscNT7-_nkw5z5$}nA`G@h~wkFlw1i>S!{hJ`&k3|M9?ji!e@Oli&E>=Cfk0eM}W>QQDX?OA+yN4{ceo3O!j=1r;pD zYgU-rCzj-;5*0X2pR42vA{y-~g09pooOzd<@~n2*5x&50={BM+3OGEN14QRpRtZeg z5SCWUujCCSfur;jFa>{Drgc@ZYWqoey|jXvE_G(YSK3hAd`!{kXYQocq6TUYN>)kPyGHwcQ*faIQdOhlMC2J5t=8Eu(v9m-8~AO7Cmif;WrDBb zx5;W(aK?mAH@1X~%fuR7@z2S;2WmNiwVJq3ag%?3^RW+v2WUf2@*T&3yiD3ED;y0A zYJ&zFeB?FQk`Wp@o!N#>jIRpgG2hMhMH1Cpf`ye5bE1giAw2-HyZOw9(0x@2o7!plJi6 zJfJ9xrH>0H1*S~Q3uZ`~dgM300egIr7v?N2tL$%W!3FT!7Ds-pPZ=Suqu>g&&_P3q zfWz}GWe1ORN6VO?-)u?R$GuTf3>lacHidzYk)_)3SC_|?@h5CH$TnGb8I#8Urg5oD z{BN*lMT>ngB!Nfjmz>S(q{24HlkJ0yB?_!9^WsR@Gx=uVIL=Zfh_PP;OnjaN2j7XD<2MVGH8X38Y zG>q4jT3{4xyhZ3Xlwk9#ypYCs`ia&8Im#5z6$VI%q8OmeS)acAZsJQs0ot79_b_+r zM*)r{k9LVan$RqAU3DoqR?d`dD}sO6_dAQdq?AUdt2N+geNLwk5)nkrF&R!D%S)z-=EH$;IVh z+h84rW=z2irdD{Gc`boDDMO(iyf^x_2+`CKc+6w^bSuh$e)PqsQ{Z`9_p!|~5$!*0 zTxmYNWl~>KH*{n_Nw>nUkaof;Uec%Fpr$i2aM?==Yi@a03whhcQ;|>kMX>tQ@=aQl z9u0T8IW*{&Z|@yTCQ?E5CjT0f1Iw_%x|$W3^F+wW8z4-;e{1KNj{=@hI#x@4<}$yZ zC^xp137jhr$p;)N3-SfiWQ;rRgpWy9sh^~3{4s*=$BpcjD;KiydIjI|R$^${cl)e} zIi4N2QCuiBZUMACmP3kgg-gZG^iFPaP#Jwt^AE>CHl^>uQ=mOBCn&l=n5G_UX%p{) zXX=yQeMSQ^mPChF(BeQy+X~(#Z!+YaIItgr4a!$8`yyu{P?pcV?5}cD(n?!~*VU#1TZDJTkQ(gLH3Ht(D)T9@M@8-{V4BT3!`KNO$U zPUu_%h!<==Cgn7GvE0;fRs=?=AP|zPrcsELTADd%p1}m48>>N5OsQT#5EzKZ+ zj383t%^EJx_hDibzKex2iX8XwrwECC6v`qHaAP6jiIxjRTwz=a5|!M-{CrNW=_hGl zi;Q`FDq~i$*5VpVFv)x;gr@brW}>Do?_~5WcftTEBn%Z6nmlIJokynagvFOCG_6?r zw-GABQT*h!+7x(I+G0s(lH+?fw8>!I5Gd?a8YXPpRXVhUXiZiLDhhgG^md9C6i=sH zVq;$5CXelqdFK!$>N4FPai(pH`0`s`;bppVK!$iN2hKz}7e?Njw#j?Gy|m0Y;SgV5 z1y=z%c!>p`cjY;g2Vhspuzh5X1vw`i0%Lti6NZh85L7555bdA93KUx?P@DQ%({Gn9 zUI?D2?%a)Vs+B0g3sfqdfs1zA_7|@5rh%Uq-;u#Q)|rdKY8t4shlkd6@Gy9hUXs)N zmQaUDEVSfr{f%RR82-52gA?+I-W0pA5?%5Eg zRs7Oj$x@lvgJ+D5jzR})vSvR>CsBvyp;z}|7*f@JDQ@Obb9g@fR!=?U4CsWYfop^rDvHj=PIMsxH?$iEaj&DzzYus zXR*MO#?q{>Z0s(UZF#XUdI?jFpK|Y0O9qFek?;Aw@K$j_JFUlmPx5EWwSD1zjJr|?N-XcX85@=(oSr99qKcuk^KO_^hyy=- zr?ilC4f?aK;?#P~Z#z11xpA!7c^V3&4KIyDTJ&ALiIF653S+@b6JP9TVn-`Rt0^{xZDt$mRj99v{)G>CCV3RWzuUZTQyCFoiGd= z%*1puVkw$R@?82Yt{NrUgov-l<_6YY@3r*EWG%;U2edHC*kpK?CnHt4l!-a;g`o&H zBVo`+@)qT@Vfo{?7M$e4T3cW$ioFv8EhOewF;`hoGmik`muY3P5wd{^;R{aOEsw@R zJ`^exLfNXRRdU>P=m~Fr4{^ASR`I7#pQSxAe(N-@x$V{UMnIEKk2t3IOkiSJCSxqGq_=;Cy*DTR8HSLEBnqCCy<2^G?Rtc7 zCkjk0#5}0V%gJMqNLnx9@Dq9fj>dn1EFXd3zNR^<=e`Kpq}9!&QC2DO`QJK^!rwZ@ zRec3_^-t@k;HZ9!6w&(3jpk$;e;x#L7%oPjhrbu6sTlH!Zjh zbh(N{40}(}tQA-3dV>=oRpKm4<;(iy3C4q;nVluAN*9rfn6Sqp%W8qJIiW3zM;X^b z5`~HSjE66si(}6ous>Ap+c}+T*unA0|C5-}4pFRywAz@Zx!BTj1y?NazD=PfLVA-}!fx>pMSQ1&5_wK9OfzzXfXi ziR=yDly$Y@&5K+p^2%k(WX_D0f2A76?K7R-Nb$0KrO8rgnr{2j=-|ONG=3dvbB;_g zcdiF19)g(;joCc?d?mG>xneln~Ok}US~Q8|7t9Hr4(;J8WDt7c9SzA!Ayw>}xX_cBH4 z*?T`KiGC~OV(l`IlDTo^U85cwq(xUtQr)gFG8ka3?5d<_rP)KNPzw6K2*@IIRg4X3 zSl}#-Rd_u5+)LT#Skx^-`~eIKWtEuiJskUf@&qSV^59F3AJ+=vVItjZWE~tIAj-^4 zwal^h8!GV;a$pT#e@C$vCCd{JDnAMd1)Uq1wA{2ewMS`pf?%}@(E%)&Dgolg^ud*= z0bhZi=l&}Kn>?}ByD^HsqB7Gc$V$`{PXT-$9Yn!yzvoDH(uyO?S6?+8qRLh;D=7Ta~r%;Lvq zWg?>zEjVRzWj=8LCJxI4p!yHnj^5u%l z#^zczKR*|RX!6vV;Kaxwx~=S}z>&@}9PhOnwFIOH?Qv;TaHyP)0DDH$0iTYh0S!Pk4p>R$#3QI z7rvsnl19ELPp)xTpsKRD(z6z%0VgYt>(c&`!p-a|`IZwyim*Y84IQ3u6vqa92xo~P$+O^ElYIkgwyoad;yVvvYG;B~CLPpEN-s~rl?NTL zao)1cC%lfz!+xSnpIfIiWqRwe|7}ZmN4HE5<+YUM3XS<)@lm0*e(A;)%O3VTnNPQx z$b|-7mhh|gt90qa!O5VLQQRvroD_qFxTM9D<#${5KmiJPNqd!FM7a|F;8q;Lzr8Yv z2^@u&rr&vPzj$s1>>+*OI8+|HN^M&Usg@I|<@-*Mvl6pajAbeOCHZ5YNJEl{-(@UF zGtaNmbKoL$^!)j6b9ZElxxFN~w&C!~kJ1RnQm(Hy=`iK-%`pakEe4QjFe(`&<3mCU zGb|e#_lX%U)xkf)0A+ZBetytF&WqL)(lY#V>9RZqAZc~Ao2KiC3&Y&lc=^Qq04D)h{-Dfh@ zGMr@iGT@@Hs|3rqRd`|@B#%tpHdvAyhyqJPv9Mf)*Z3%Ln~k^5vi>rsg(5*wsPV(6 zTu+jGwu{TD z3@Q?9&o~H(l@kR{@GsFcn5l44NWohvs*#424W+}hnUH|j|ChaYjnVYD^82c%?>#;D z`-OLr6eX@iQJ0mpH_BN%dyR~&odnp85hHc0-~M~@!m4DoyInx~~SLMxD^^d3rIUb>{uvejl9ja64oOn6JLD}3A&cPCacLV;~P z+x;ZJYlY03m7mbew>rfX+2LVA8j*#B!vtpOo*ldkM1Alfjvxs zm4gk!qGG%QW0lFJI9SgFjH?0iw@*?=H*o-gdzveJ z2N0>Zi+V^NIf42ipvoW)QO=N8T{7Xfj`?!)sFwggrAd;DE}%)FExks0py!*TKWO>$45R zs{IT}IQQdW{=k+YUhz1gPw*V^;rCEiily`pIPNKL!q}bw_}f0hKks$?$O@fBhem)Y zIPxuezVOCShpX4GWplTNn;R)PdMFbs5eu$-+t)P&I^XUtZ02?GVmGN=a8i@W{LWKRp4l*tA!4YDFLJUUY8007NXQM7Vt-rCdWEe_ zS1dUok*$jNb&@vlf(?htS-hAcNRls|w(W2MZpl|eSmc4_{U`j;#Pe>HNB;PoHUpRj zb4_r;3ONyejAWSr(c5w^Odk5y;AQ%U$+qHMHbJU*SNBpo`BRs0NckI(bzeD|WTM}g z%P9BBKz-I9np56>2QTbL;g?#G=jIa`pMir_Sm!ov8jU=*&4L%-=~wm(X`*gv%`veQ zHjnqKJeju>&+N8{bd4z<35kA~Uq+AmVmLN}qw_TO#o3id#}a>wk&u*+PMbO}6DywB zHgQBy&VQk4z`>O;`4wEzBaUgtv+H0xGSP8Iyddu;~k;n2nZ0u9ZoJQnZhyCsIGS;1R zAX(DGSl}jl+sluWPWN3+j@x#QQ;atZpsrY1hfX6M7$-S^Aq^`k4^Rs0-Vfj2Fg|mD z-Nm$vhB>%xQ&0`|gLwM*hdas-=Gyzc$AKT@0P>_Kf;DZINBird)9a&v@PMH#RE)kU zJ#o@1YL+i!0FWo~j(}CsRXm&u7K2MAs@c@6n0xeHgnsXql&7XbZftrUxy`50+2+GA8^&Vo=+o@A0^JY_Dxa0{sJ`OCt#aBLT zVD!ja!$G3>$s|6(jdp__;4Kq{Z+X3VwftYa@T-<)I?4YowBTq!T|q>OqebFSd%fh3Mq_*3>X+8AEz@f5aU zWcvtmHBMX1)eYjP@v1b;^48Dk=~-~!Y`1S$e6TO`a#h9+rQ+)z4x}M%?(v<{Y@e9r zZM!~KM<~Di0S<<~4gkoi&;oIkm$W}PiI=Btq_4v-aNzb1Rwf>Mp%f7h*_rEN2uoY+uY^> zTwwaAp4N#qK67KOjsaH_B4^a^L^^T@4@{Y)i963aeQ0rH(S8fR_$R-tu}|o#PJ3~% z2m;1P+b5(+SS4Snfdl0K;?^gx{PuWF%lM+Z^l8c|v%Y~?c~m!Qfb)Ni&`jotUC$8vsT)d76OR~-XpqW8cgv6Zg(CVA$Aox838 z?Pl6l!+fW+faQHi2jPK`43ME}H_6z8^`p#z3|c6tC{+}LiXbl*Lg}bDT&eNfm^>AX z9m$wNFMPp|O3!=!nM-XYhbmBwvr??`P%({X0s+=zp+Zv8M5zEICjeMLr@w|M++1s% z#WvyM|_=t_-4+-xJ4{37HUB@`rlkG_q4m7^PT4gWJhFacR@Mt&< zKi@Jppaz%<-6`XbQb9S-fn537Fh?A>9`-w&Ef0uHEc&L%cqD<3nL&NUr$s&3RFM!J zfMwtlP5x9G$D;HkH&URpwoBu0fP1DdqYPPfx12w}i0qfzPeSvKK$B1Q~D+BULL zg;G|Yl}tfD?Qfmd)#>!PGU0%)VPxET_?7l5{L6!;{5ar?Ba;_Auf~{n!&W#x^M5xQ z#M^(?X;LOLKqAv#`!T<1ja4C^`AOTyvrAbUSPq`r88p|1?pGB!I7;ve|-C9bzO zoH<~p{j2{BKIq4n&A5q9!<;ICX*+-?eaSasuJX2EDg&;@ z5^$x%{^z~2r+8BQl79Qhp~Hu1pV`C!U%!66jQ;RigO7=gv}te;DMlCVI8?*qE;~rk z(7tI0@q;eDl7ho^GL5 zG?ayB=}+Jp!31FA|U9nXS;f_0!o-2)pHmcf8rd5nRAg0p@- z#nz`c$fQw_a1NB8G0=7i<=^ruNh?x;S))e2bi}xzPUG@aX4c_SruP?!nT=wO(o+E& ztLsS;oN53(2MR*~%1i9Bx}FN41F!$HK5K;3B+tvP0R@Fjox%|x^EvqNn0vR`H5nDJEH~Qg)IQ?@rplwlQpk$}->#Izb)-t9Z=?KeDNl6_5yDR6y1gLGDNdnJ>AbAW0#~8Is#1NMzi@A`EU>yzAZ0KUTaWv+QL8Z5f?IS!ygdOmmR@! zwy$`(BBYV&Lvc;wJH|M;$nOxY`G>l=KIQ4`GAZL2~4jwujV{#P_;{;wp;oFSs zZvI0C+^85HaBl&8eJ&|}Iq>n9xd8WfKgWDiLzL6&ft!58#xf~K;Vy7 zV&HPk_oi(EVU@3VXkOvikE`bG!Uf0{~$L3vIT6V<3~bEl!wW?|^;RPHj0clSf%`a1ypv z@~@$sHc`hb59ma@w0Y&Tw2Du3wYr1n2?ybsAxp}6yu+!-TEG{iyA0ay>W+7>UT-(p zFJK5%-fWB>5Vi!4b8YxXIANOrTbzZx$xNbfslH)bD!V#_bP%jEhGYXT<<{5Od!td! zDN`EX>QqnlQP;IrK%G7ckFweb4Eut*kE{t{ zw5zNmPmU4lq{)3aU7)Lvf%3yMZUf#b;$!ATs*Uq-bGTYL-WZah#EhTSj_qu3loq)%v0 zEcCFud2v6U29@@btXSNvHjzP5+w@y(h+eybnq}nNg1(ooZzD8v<7?IlB^gcwrg+^C*WF8 z_zqUychY5Deb5d4;3Cg8!p*Ckn0(f8lSa$!Y-+9N8XkvuRlJ7!0xKx-7(=52dfG0O z?-;q>bAZeIJ14lU@=j5Hmb_3Zp9AT0T)P5V@VIMGQmedQmG=j?WJ5Fq^ z`EWy2jVyWO1c9tOlSQsIx`kk>za#_+*i;0mP-vo4cQp6VE-8O|@i%Y<+N z=D4xY$*_|w?WMPm!W>%GrNh#n4GmCpXO=JlIa2 zJ&p*Opytk1N%miQ?}v<{<%8nBSN=~42UHY_ zl=9<1VH&=2uM`FbCdw{`HqSLyOG8P&k*0B9GT z9>cSV@=_sg8&f<{DJj;VT1qsElNfcD@GNNFbn(!TtKee24h)t{c#eI=L4f-Xyjgbk z9?3ym{8hFZS>_2(h_H?4U7Dd&gD2h&iw%6Jju$Y2D?V9Dr4H*Eo<#W+uJAg8A#?#} z=(${@#L4Vnm1_qL6t)n;BXEl`Z{5@pIxw94HtE$BV2o&JN&5gx{gF4|QQj8-na5aM z2P5NE_2#N1Ya${}v?Z|OO=GZ0vQA)n(uU7NqxF%(3crW@e*1ASvW>%Q29M{O(4ggC+93Bmhgbue7tF+f+%dkZoybp}J#0i?gpa@wS9zpCCr|u{ zSK1l8_c7V=C_ck7%qJWTnJoEB+>ortls4qLerrfn-F{0$svGj^IXA!c)hZ6MI+QKh zRHxg*4)LB%Z@!X6>+08Cjyf!HG}X8A1nS|fvhH!4L;DZk%8f^>xj7h@cj}>QP-d&` z$H?&d*s~5?~ z*cn3P-U>OH_U2ooB}d>vOBq^jcs9m3y$pUA-$EKqXW!RLX;L=y&e|Tflk&PQ9wJIz zNo_H4~0AU=Mh11aLx8J>-O^k*d%0I`CO$_e5Y-*lkRe`UJ>Ui-j$ZRFNxXRXu)yNv{TW3|8>X={Mjf|I_E8%lZzpQsV^N`X^`rJ@z#Q+W(xO zumRrvKeiwL3}KnbSsRX)wkbN{k^~E8+z=6;_vVD zU+@^+koMY;tX$=q6$j-?JuTg72l|_RUHX*C6<03cn|%ecAFvcAgR{KgZzsS;OM*Xs zLua~B-Tc6Rcg-;Gn25Olk_O~$z!IT+?L4nsdw=&h@c+&MWJ<;4pp@_ju1lsIP|CCB zU}0Qo6oxJ%{;O=e2evB`zkLO~u$J`nDyqmQ5Yy?p?5dQqT#eGPu0fQetQa1tOQSv2 zRQbW`(34^v;*|*lZ@kQ;fB~ec8X)C|IO?r(FfIc}M(EklW9t3N&9@3#?~MP%hX>-S zF)(0Z(cFtC9ef-(97H;={MN8D-cKdk?^}-mM@GmmZ{bF~l`3YmjY?gQ?RJXdu0CFd zabc;^=Lwr$XShwoVxO>~%LAfhLxd>L| zDdqb3yBJ!7_ndnB>W4I&O05^;z+y626fMncY^ciBaiT#bK{wuXq;N7`cPhalFr?`7vPjar0`j7RPb z8lB|jSn?k}C`U=cwmqRw*|**7W3Dh*ZXdWPFU2b-S@wT^D+_UFQ3j{$>Ly-qa1<|j z?9z_Pfb=0FBE6^%rqTpZ-eXf8ux-D9HA27Ximkw4`O z#h4ADD8#%l$VUvoQ=F!tVx4E;E`{O{gmC76!Rh>rTt~tBSoIBHl$8n&V8JH^xv#?2 zDQa=^4p-*Gvw=j-lxpXw1+-oemt*nkO0Ds&)-J@pf<) zC%;Xho!=G_vN5p1R^Xs&iiZT)r68+*z0je>=kV7;D%;%Wp7;buzOBPLy(c#JD_RFU z6}BEs^9#$coM0W^GWm!%aMUy$UP$r?R<@E4}f(muUDaMxJz zjg$`3%1PSAHk39DI5@J6XTc~rlCKHJ5UP3cta>k#KFUj{W!;Y{u3Z*`FEXwn0N}`? zt478Xi<|tk?c5v&;8ZD}jH!=>$UT0x0}SGa2`TXHht~;BGF<7z@srT(#x&1Rc5(m* z$*as%vMs$H#VD?GmF?$#CK7MH0^K zPvH~KsW@8Nc39I5|WqopK9W`;T6%p~!5H)1S zJut7o@h0IecfteteE#g2Oh~@_?eDjDZqBv+Y__I{MVC(DL8lekU5x*mcWx8v#OefW zSwtReL)(&Ax=bDv2HV;rQ1>C*2|0p)ww?Ue*jE46OPi{FVuaeQfvJ8uc!-du@q=yg z9^Pd=@VuO^%U5^+pJ;)$dK%HdD82i{%2nyuQ)PtNM6C_!r1-r8RI&qcu}UWWUaFiQ%8z4D>J1dO6R!mLgU&~ zVj>f?hxe3aSC-PpHS#GN8D*UF-^>h?1}63DNM+rX#+u04$9>V8E)%;vdH6ssb=L}& zGH6KL68oYa=#>6EbjbG*Qhb5rT}!)Wn0Jh9lu{!$(@uh>G_bAr-X}YG_OA9g@O~UX z?4w*1t@{T(m6}S+`%wnUmcqp!zaol!t3CJ0+X;d6pRz?AtG>WJu!np$vIKYY# zq@kI-W>m?Vb+{fX@|d;R1BapvU1n8rS-zB68AFzH;4|z?W$5!%8X7?X!=j952RD>s z**6(@EvM2~p{i^h&^Pc(l+h%;C|KY)`G|pyQZ--tBo$X(=F{ZRv;ank^wnD zCb)qfzpD+z9ys72?hYo!_kj)y2`9_84D5QjR^YW|Jzc=qdczVjP@=4*FG;XeAN;sR z&xdCkk=z6q4Z{Gpg8Rxy1OL!s4yq2w{0yAXELh4*S;29jD>%wdug?teD(C1h;891l z8EwV+o5?4L4oBe;cVPFFi2^?}n32fM2J_7GoK8$_p@VAXDZQ%(xgj3*ExE4>m zmT=4D#0b2i-pk5C+NTUgCuE&Z*`Nlz$Oz?xugqjJDzVgQGb#t-A@4p z^4YeFHx*;eIAwp!L%o^u!%rrHz!1J=MltyL8G6OJSWz;Ql^|$M9Iyqf8+OTKSD~h% zOD}2U5MCI=?c_Qy4?;FvDf72@OgRYx&sB^|@h}WQPv?dmn&iEktj65FLzoKf?dqL+ zR{03usa$;L@k$#jJ)RAP)mN4JF%3PwGr5tE)Emz#`bML8oe9F!esqHq8bX6EzI~JW zkwcHS9iM6U;PaVNN86b*CkfxW-QK+TPTRt$lLzC3$3P?)>;u2Mp??DU?Gxe`-a?D~ zLt-Oi-qSI2*ANTtIti>v4my3*UPx{bUd5xYL*?w*)9vnUUT}5w7BmDZG^n?VKeoAg zOdi^%C4%KFQWwWNjd3fF?4vwMBd;?-6^4@s`6h@y*%FS8npIBnD1YYjUd92QD=)5I zXmpPuqYJ!cd4n^@)w9aQ25%x2-n6Gk!Sif7CNsr@~f&rj%tJ#X?oV=*ye&XhtEIakIK$>|WvI<|~oX zQ#Ou~Ax!J{&4%I+h5`*<;wLWYo80zUa2oD;pZ|d;9j5qmdl`9Sxft)z0}hxN$=Bh= zI|U)0QlrOD56P`tcjDEO_YRwSJH^cUVq|K852-xucyWx$C&|BHAKVJ3Jn$|*0fiw~ zc{T|z$W4Arn>gK{@{4=^Gi^WTXYTRc+gSNAX~2*%*ZDo*VLneWkbk};B!++kKhGVw zESBZrp=|DRZM?D9vNy5_P+|G$@STLs5*I6e-o9(df=00675r0Xx&gdXFUC8|eaV%y>xqw{!x{C9^LYO-K!syH8sti$){rR^yd>(T89}lhF{$4_@5x*f0y^iwT!20?M`hWdVn~B7a%BXWak4qGYUmZZ0$#WmK$-Bq@4oQ3 zN9#`r6Xo5Q>uFz(8R}s;8yY8FMFPe$Au>6ioYRQhU$Hq)9Q&}!R4c{u*?u({!@DLR zHU6jn>q4v@q2mVz!5U(1Ye3g&#QPkwu2s6JPT9Y_xcyZzX{H}HYe;L;sVtD3eB%ceT z%42=;tR`gDw!*9l)Zk%da5IlQw5)j@5QX2V5SFx*5#Omx6*0`bBOgqDTSv#UfBVe) zNdo3F9z>OKRW#rY{@|f!&rFsOhDp3Te1CV|Zo}oC#@B6*((7L8ZgBS4V9VQ%mT~Yi z1xG2wSlIr@wS%SoqUZ73a-CKOX_NWP~=MM#`9s5JugL~1zcK{YG}N`ja0 z>`NNb;$B9Ak808*pA6l&i$`2u#ha=1j^VdPsKF-2q;z=nALdhdnA3dt?Zn>l(mkTL z6&!6h@sSXv%!vd|>rXKlzP+kY3YY&qt;LvUWybQJh}2(RBu*O3o;Y)dPz~GAwi#oh z06BrVj<jB&@7Tr}5Tf?c|AL7}mTYckVXN2y?)g_SFu? zy*pX*$UWn(>Cxe>cu5Bm;Ge{vPvVL8w?>DuBs$`2TWWMUfr-qJOY%Jn+vm1CT*}|D zg*ypB;_bhNU>IJd3~ATfOE#IvmGp28-$yV^@2nE8g-0Et!;iG57(|B22QRxk+UMkz zxcG)ip#~pAs`#K1lUz|y*Xb3n6{cb1CbSz8Q+i}9XVVo_+II|BR9}?tkWU;zxl;PY$(nDLU1T+4op7fCkiZ^{ zG_)VD4#0KT;+$I$`a~Aj2RSS84#+V>Sh`V z)?u=~z`;`*Z%{4@{*|LT$`g(Jx_fZp$A!@9IaqANy5qCVtuv`GzB*I@5h^;0B2wgAA;S0 zPr*@+w#(J6o!`LhvSc2;;YMx?EbYyJM`mQe5DeQ$1H}5Py#{dP)d0|#Emb!?ytn_N?du>~5gos3pFn)`R$$BToxfUr>wX4R^K%0+XDj0GpzjD^V@Op}h3w(a?AEr@SL$Jc9A|VKn(>#K^YB6L^iGcf{9CbLorJxQ{-r zXLu4A8aLH9OFyjf_FTQ|ZnD10ae;bhRUw`ZA~K$^L#@#jqVEJ?6a(-G8(Y z3phTjBTkDCPAY86Rb)YDhO+|=;e(vx_^4m#>VKAi|eBl|MecIfYnH6_B_W%F?w#8vx zQe>wthrd2Z4!)F%-AlV@n1j}UV^LB+x)Am6puU}N_Xyd8^rOpxZg{9X98A@%DwllF zBF3>zOw@9?=UrcZ=L$+hc~r^QAgIXY!ylEeGO7YHPrN?d(oj%=MJYxJS3ObwQD}5T zzR|A~E2B{8g6kg9ZkX8`Dtia^>d-1Pi(4-rzd=CnocC35BE;SdC%0t7h(u8Kv~x3;%2ghGz-tRohu$~- z!ISbl56``kcI&+ICwVDkd1XP~SJ^6K5&72@Dmkqn`TIR_@VbBeoqXPhM-%W+{#i%C zuKWC`HXr!yvy>d#(4QKIg+m#;;uwCCN8SqG!PmNNZ`)ruPHr@Q${0}23M>B#um6tG zME=MxHzPyV3vA)?NR$HK%fio%bH21_Xd}LzfGLxmCw|AfM&XeyJ!1~Q)B}VVH+*Xd z_{QCxB-iLrlEFWpNK>CccbRiT9JfP=up+1Sf%G@)(8x*$i=0V|z&tW;nh*y=q!Lo1 zfC{D)$+hva{!STwt1n76G;YC3`G7W0XPKHlh~8i$HhoNw_No(o+DBtBeL2Q5p+kD} z`6vE`qcQdxFC``Gi)A!<&-T^39M*%kbkN%{+UpGdO=5r}0~(W{FOQ7J_QV%YT)KU0 z9s``d2>@loRWOYnPeAcRlX*5Ix{)?3gc?rd!A@erQf~$}{(6FXJ<-Rb1N~>1#Tg8G zreCR=ehaKjT^41+UkP6we+Yjk@GicSvukp00>6~k|=*HE$O0$}Pm zQ6Je~eC9?*agn>qmita@kDBm-6UpapfYcCm0u!2$4dvBqhXwP{NZLxb`;##mOe1}yZC z8)iKR-MpnIKvJS4lMh%2mU`Io{!(5&ZN|xDYbC(vi>}p2_RTfAp{$iHjeW7k@*!dVN9mLHZ+;-Z#~Q z@DTai&9sY#`TL-SghYP)9v=7a_sP9?YmWmz%mHLa1>lbHyC?&X%bLM6rl*Q9@Wjv^ z;I-SOoNDm(ZIOMG)csy!rBLOeg6m6;Dw`-XH8R4QL2;1QC?yqB1{KOge`zq8)j#O7 zL0Ou`*!4OwLc^=Pget5WcvOsKJjEaZm=grcquw(BO1z#t57~`TtgD8n9S@Bm=`lrN zGXR+M`bd}fn71p*ym?gqsVvFoeSRi&nEu$lM=tZ^nf%~?JccOp47}jBZ474Mk|w=0 z@P<5;Og>ptsUUa5F}&B9CWGxIjV5^|e1BpJNx(v}uV zd~x{e&%t0do9Iv=aJ=mG3YQcdxE3#Y&7b0Hp4Ri=#DM&RNzgTMfey+`pD@fX^bA@9 zE>hF$(zr5H=Yi$KOQS?Ogq3IFNIuK?F*&#cH2J!$`yzCb~D-u12+LfFCUHQ{`UKK;@q`gKCF^m9`nF8SP~Hq_8x3W} z$w`=#dt;Yf4KhAAArQbYIkCuuN+XN>mNlHkP!%_r`wV9daSlbw6$USP#^f6z4;uOxFI@~B2^UfR zk-5Y}1H?MTMUN^}0~cw^#Hn~@MQAR*+*ml?o_gYJj!hh)-RGDT-QXmaeM}&3x|vgz zK(R&nt{jj@A^p)Kiqkw{B{248MQ z6gxNbroT#qZO*^6EBMze`Lv@%UUQSU*{7n@ z%?J*{P}Z{w5P2QpWl0B_Z0h`}ezt}@7&2B~`r?Omxo;qjAj&DP&iKL?nFM@84aL32 z1L@N9KLJhkawFdXK!NBg@$#g!ObpW^a2`;ABn{=g7PK_^UH3~#*MOD5#s{RXPcP)=PHx34z~QJDN;u~_$lPmN`({r9NNR!L=7SfY9zQcUEZ=9 zWTGs=TN})FvAiiX4)uA8i;z}iznnEUk~TuSJ4-sJg|*I zLm2_yjxldW(?FlHVG7J#4Y<^8f25w|fsUTUrwo0o(!)x7@#(E!L|Bc)m65hX<50Q4 zsJ~a$d8C|mV2{Z>#kb&89l5pw(Xc;=qje5++vfsvU`XoK z&ZdZBZ(ou&^UDhPWZ&!)7N6<)rpIDKZg8^7!Q|)0kgVK~?IAau5)?x6-u|wq+ViQc zvwK+W^Pa$Be(P4|HR3epHZ`~aBMy?`QH$o+9c<%rUfWE1@~vS={4iw=<%u^N{i;M5BW);8 zElL_QU5=CDi~X(IL0H+W3`Nr9$u`og_j{W7h!g0?C!hRKyK?PTyY$Yx?JDPYuNw&O ze|Q@sA8_`4Cnz43Xgk?%hFWylbu6%u{Ut`YatI&Cc&|l5FO=_uW|ZDn#?%=J9l-$A zqaBaB%ngQq3(udW8ys7z4_7_5Z6;UXW0;Zkxj`~7Ma!fb8RDNtvR>Ywml}1 zmXBU{0)CKVL8&Eejej}ct^k;v^m#I(x;>K_}a<9CPGB7)E&~%i`(kPdxoR)62iQ zBG=7NPJU;y$TMZz(uOxyTh-VIYtWk|Q?63j?`?w_CTWIFjk3aFLG{S^*jnVq5UJn- z(EJ0oeb32fRtVs8R-~=)0ZYPd+MHtfHAcG0T;KNIzTrr^@mzaGfO!J%kD;#OD{WWrY z*7+C%Tv+Dkf2W}v(J`<=xD5V+D14tg5fCO&^UtT&Uxrs1RZ6I^Qzz}Pox%j!z(uO& z4@pR--1obF@SA#yiNK=$!NK3knm5umKDUe@&6I0-FW25XXmolb&qVchx;rjHN`EQv z0Q8P+tqI6qU{)0hf_H{ATy~nhYyBM`3kM(WWrBrV83SwmE33Y_vfA2r02D9y3$1B8 z^ExrZu(DnBf*X@AZ^T73B3;3?=$9`VTjil7))hlY+&%51VDVhz2|CwUosyrPi9IuG z%(?Am-4G#&QAY80(woLk=7gEQdNHF}6O z_Qu$-?U9Rmrj-qk*;`*nKLXE9kk@#-r;`MTi${Bu_L6AZN5g)SV=RTIuCmP|c@S(} zWXV16Yq+ivV)LC>-+?0H=1 zdiyE|hBbtVr~4qpGwl!GqjR`#*-Wg>!&&b$GkA@`MI3>i7S$<)n39)O6OeFnknw@~ z458BimS&R^3~?)-f^gs#ZK)r)#d(?uBi>D*=5aE|OK3xL94+wNHj9y*HXz)Jez=Uh zY|%E>KTY4<@5W#H!UTOr@&OSX`rZjZNB24ri2em;CeYj$hLbgQvwW6@4aVf?U5R6IM5SZY_7)}y2a*Z*y>?%jIS#i}XZf;tw0mlJC?}!;xni>vn zwJOWfqA(v$~>h*Nc0-dMk`VB}ps z)alDNguTAC%B#pVdgV2Ang^_Ez;Qs}KD^y73vA12oQQKRftIIIV>LV!0I3G0P{^C> zILnyjdg0z%;0$R!WpH1^#(_f~`AI$0Au7J?r z8BCFdK2UnkYY@r6o)=b(KhcmJN4?MnJcr05aGzk)5;? z#wg#B6{(F}@SJkuFoMfU3y^$T@xarV*wdzDU1x$I^4vFFpo8G6Upj9CQzjbghbZN?R@za5!q3DFgw({+v z3(NP?-nR1^^5$9h8brlkQOB?aCszno`Ak9IEQb%cVn{W#9W}y7^_-Ig>8^2zi3M`C zzzV@K$7`No)1y*tKQXLCzPou>VPr)f-r+dVi#PAJhY#$h0>&Yp8`2Y+smVT;czWveG@&gf5azM>0+Kdj z%px2|y(I6I({b9k+D8BnPJ$fpRyOKN*{J02ofMtA^Ya5=?Ve$d;M#DNM7{`+9%5M2N2Wc!RzA&t{Ar{+8k9yQ3pYdA3c2z z;12v6T-LdnR~D)2GFo*&uG&qpx{m|=Wzn$ew+^u~W2n_IgIn2rJ(CLK+LR zm5NEt%eSfUS!O%gg!Y{1E|MkzTfXU6&nrCHbdAH-;^HkeuK#7t0 z7iJ?w85D)g6Z`SmT?8;}Dj%;>P*9Y=UCXp>;uf<32`V@V^ z7vl&+j(Wc)$c9E3yNi)bU)2CFqq<&nV&mqNB+6v-9=d`U1dJ5ln7EQxzKggt8FL;3 zkq`);MS>XjND{QW$*zy$kuxV1yuk9Rz6W;hNp68)&OSdv9{)VinpESBbSQdxx z@<>_LST?+X5`n=}5C$M{W1vGs8jAGuQ2^2VDhW9HhY zEK5X^^)WG`Zu!;a(E8WmKiM;x8v#crPx4jV)kQ^%v1`Y+Edv=^Q{ z-+u9P&ynXe^_Gm>n451ee(#O;KmGpKBa`FEPfmW(aK@RVcr4Qo48;-W872$&2=zIC z8qSxRO6*GgJS@?ly&7)UEzdfQHPdw z(k_RmW)+(UDn+!wxy$Ab@(S90zo5BBmz{ebLeRR_e-d@v2K)^t7Xaikl9kWU6#438@S?G)e}`2x`Q) z%W!>LcfvdZD>GE6oluI}ad`O4O-idDvsF(DpHmzp;ioab)eWdrbC>cF6Jpa#0-V^Hur3BJRw z`opz6k{)?(lApaz{N0TL@y*@^%>B?VZvojSG_`D7N7>2Cz=g-kW{2rOS?#5)1`Df5 z>UHM6sPM@byi2=kbnq{RkFW_?$&zx|%H$soQ6T9N2o0%PO@n*UlO`9A)c7%4FY*%=j>#}l?+$vMsjp&`@IVuYCl1OnvdpGWfHKgTY}kzZZ4qjp=IB35@Wf-7i4TFCupFp?#QHz zy8JG$Z0QQ0X-VrONCJNM@`L)oU+@K<#zgk?h=&g;XMJ`ejn;udJX6k1%7&9!4Y|^j zew$+8!KXDQ1uOUFA{RZbTslmGR?el{kMOrw>5{=el~>1&4u`lfSHz4>-vikNqFk*1jSrR}UAUM(zb zv^VfV|K-m=-+uMye!9)#sWu%szzHz)uR|EMvxK$W;#k6e=W`!xzxroCm*;c5@aXbe zS6LC9ZV#P2+I|6J`H@q{+JF8J{xRO?np7$8H*vOj2KhnWVmmfE(+;yKc5!*N9boh2 zU;7I`+m0PNn6RMlUwj)_Gws8VKGa_L#7EmvuK()a`At0Ivp74(n_Hm2_{pc+U;gABz#!8i|$}k?HL82V6)DQfP zCksK$Af~v*(8CKtw<3?mVi_i0I}k;AGeRkw8Q6jCwVg17AiqrxNTv){UMR}kik_#u zo))ib0CaE}Ha`V73}*iMOeogll}3?;{KxQ;`xF!tl$r{!l!4EEwKreeFjN$xB(54F zo{Er33In1h3hvxu#K!v{=0{M;MH#SB5CHP&( z5VMxRV<1%NW_1E(EWI&s0HlsyTGzW@N;>=mFTF`72j}q6ywFNf@U1~rUQ3?Ff=Q#p zTyt}Gl2;Gr76Z6Pc#U)FgkDAmR>NwtLD%2H7oq$wU9){J(Bxv}&tOVk>lc4hZ!be1 zG(hjNoScnO%nroQ80As;B<-G*;XOl$dL7-U=)Z+$o}T*seor~ns1xPP}oo>XDGR_KnaC$&oE_^3gyG3ta@OgDK+%?7fhPN{Yw3`T?7P#XH~_USov zF2fv(Bl0@r1*h~g<$+ZvQ*fpJnh+tUP9mMiVPx9Iwo}QLvSJ@F245LcSJoy&+XxVm zqfv3CJ||%EN4ZTcwhuhEt;6@=q&!Pk-OnRv6Ytmu^n2@!_j~wOaX4X8CuU_Kn4o8! zj3-1D!7 zm=r3{;4n|0_=#s8Zx5e7(O$cJwSDKE8|@I`fo}FxSGjj#0=|j@n1Po%xV8W4-~Rdb zg-?Av{D0-*JMHO5&W0be(=*Yzdd!b-Walq^?&CrBcmMF)?SKC2chQ~FH_|@$#Od~z ze)co%qmMt-e&tgiZGZR6FSZlY>f(*|S23gy5@Pb}zx(^`=I!~mgrWYetJmA#{C9sP zM)|j1yU_lNzyH6tw|LQ%djWp+lOJjS{x7}IKK9gO?bm+t6YU?r^je!mPWIzC`3o<6 zHaz*wFMYZFoiBZ*&AaLe9e?qYA8&uAv5J zKUD5!+Ev5cfw?+_NCw;BYNHih^_@oCO)B9)9 zN!^(=aL;2N?@#s$a^ z&n7$zaPp!pMx~_eWtO%eP7HXER`v>WFQXb0G5HA0=B`{0&u{+yriXsg9G`n zQNj!BO`Y=EN_eJMJ=Pgt^4h^$0N+%G{QXoJ=y)PMR#pFiMQ#wSjUAvph^#jm5Z^ z4*>egl9O{MA36?TCHd*w^eI0uAiw?gCCNFqkW9W2GM9&0BLA8cQL<+b+5Z(L|ca8}IEGfAR-j^RCi z?n4jb1z&0}zIusC@HG0(30OHjLNtWK3Fv6;D#ug)+;dOGD8IqckAL&;|4w@hPrHV> zn{aR8WSH5S<=Ee~_Q}W3w?p*fyLig~!PmZvd~CHtC{Nq{zy0UmYR{ZK*8U7e`T66A z2#Z;291z?-jiLTC82qN|x9+t6?0@;)_BgTW&m29`p8xPux&Q0G_cg*sM%(k}PDdyH z?H9k(KJ(0D?UNsVqCI`)c>5pz*^3;UFxo!<)FbU6vF~pX68N9|ufN@%#8ZC)2ZZO$ z|Cj&dEA6MAd#*kEA{1Mt`ACu982MJN3 zEZcyQCk`!O;=alTV)d{|eLc{=paA5Uy;+~`5`6pGqjWh&BCiMg_ z%S9k^zlQ>J{)Z>ZaiNvL3#E<$r+6@|MgYq2AzxMB4Ow{@y!gtcCo+s-xWX@wot)5c=1Js)I>tdhZ3-@W zyI~&ugzvU12A6GTk~iX&up02!^CzDd7x3JJL)rOI3{Ly-62}`(84Y&ZO5@P(79QKK zw55i6yxYz?4;T8LF+wz+0RY~@n;gzocPu0eH*F8~lFzr;(CCqJyab8-3ruh$YZ{ZR zq>bPWwvZbT&EP7KJS#opguscRGF|Oh^49T}MEe!|qfD(1lnhs!4{`_|>8rsA_-t~e zi&Cw~h6t{!oU$Z5sv3k1B7fS65}{amU*k{zeWhiK|(;j`qHcI<+t8V zILI=voNSHZDeid5GqIXA{+f`hufO_c`&+;D<@U9=ueJa5v2(%Kyfbm|$QAbdcm^U# z-ef`v14rm@Zo)r}SKj<4O#XKC;Nf=a=z;dmz4i9yyVqmTf9LX@_K}AlZBrQY#(;nI zrSE6Lu(&?fP98d(6;u1w0p$JeJytPEPKuY|!wf4xuU@|1{>I<_jduCwt@a#~{8{o@ zo0~abIDCqJVv96FXVwU5d5Bd?4C_(HzKwR87gjYkI3LEaKhBYtCz*Ua{Mcjd?YD2X zm!Tb@VN7LD2>Q$8Mb%@MuUs3PHbQ;yl5$@C1oDD@{jC<{I(=yO=qm2-0z4I#%ES&+ zox3`4{iOnNAL{8O?LxJCAb!L-pz@t%Dc+@SXXOs=QM%Aw%kbsRu8t!!sss7j>y21F zI~@w$T}m2*R8LnFIVJR7X)wjOr9Q(;(7hQ{eTA}vt6oKu`Bdv=D7n) zKXX@ltV5phG-!6+;4^N=I1$IflXmgQE{%+J3?#k{U(mC=N(h3UN*L%ix!~y_QWFx} z1f!6pQ{kv2Qy-VsVV$K^gq@^KvklUtYjF`G!o$y9r*Ku6Mt;rF99X z{|+6COc94*MSD=6C|HlLlJ~o01U@Q=c_!SFb-kK;bv;Yk)uVin z5Brd@*akD zp_~~vK8cZ_UpsBe195dSJ=H+;je5$j)oQV5U`BkgaT_!!1idAX?*YqfBPCaO%W;6d)dv)?=EaiWw-4RD;y3CB}Y zme3haDD|it?l-AnOCNa%ukP(TH`=KqhugCse!6X-`)(8S{puTUwl^-lO*k2{37(l` zV&F-C8Qkhm<=aV5T}zW~O4UKowlY@U7-tbKC&|!PDPs?P;kjp+kd3u3;Vr+-#L^HC zb-``=+h?A5v_1XkBkd}2=YRD2JMH|j(_SY$=81Es+FyF%`Su%xvdq!HwwS2?#g9MMKL4@u zfd9cO7uzw%KkAsHKmGN;_uKGqtj#g8H~>BiiGip@`UzeyZxOeCfHUaX*t|$zCH8-La-_ZW=FN8HowtZlXJa>^enD?t~*9>zNhaI%Y@b5rS(NP_y6EHm5 zT~hX-{U~xk@83~2tgPTZ*^e@J$+cd)T#7NC6TLe08aqw~GnL37e^H*upoY&l9zTC; z=pH_Ns2yUda+#&XdE)eZafyb8Zzpqe%?N|1;Q%Lyv7KXyG9ereY8jjzxiw}nrudzc zBAk?Ry9NLaJqHAEqCWdY3?2rjP4KWANSAn6#^q%5YhXCw*!T71CVT)zZa(R;E(hQl zHl)?Ea2b@sci@z^ynx9%HU5L81HO1`pw$afrtx%W>}gzBUnPw)6%W6lWom}{!NEM% z9RpJatMp;7#_<+!7^$Ux2m9i2#WZU;7)x92AZ^kh3}MTQ7!JTzX{XY(A3P!NrlSyb zVg@7}XPHezRxK{p6T=rix|vFxN5DhgI8a-Ksk~$Gk$iH(0p1jY1{l#*lCA+xO0$R; z#)W*;5cFs|+gl@0Y*Giy#CoXq&+gAe!GSt$>p%^D8_>Cm>=+tSo=ZKidRD95sW7~$ z$$~P3k-|s$v@VTN!*cQp_Tl_{PfNh;Y=@X68bYDGoI7zc272CLi1*HTQd)s(#8abC zm{LaDMK+lv*p|o>Ex?Orh!F>OW25DOD-7Eo%8TD5M%3)=;cT{a#o!31D!5{y?CD)L zUR!=n6FVzEu{rnzA zgWl(a+5q43RFrm%p~aPwr5xC2Y!}}WDY?Gl-3uoxdAW@81wzGN&o;+P5ADjF1~pTT zv{m>5dh*P^;y=%EPCpZGl6=!n79H;gFYbqLQaa#hdn*6d8Sk-$DN{zPd?*SdjxZ&^M2WiiVcK+Pita{z#2tR*MGqF1N*rV<2nbU2WP>L5{ z{C2y|D$0K3{DsecIy}2c$i_YD-lA{6`sT%){GH$_?7loe*Saq3(r5(o+Y&9GA~*>0IWv{Iay*g<~RP&KWZ;typffJ6~aTt=}*e! z3dd0$bR#SEI{~t-pFDOLITRmtJ$)G&dkb&(fA}-c6C-~-u)qBMciJN-k3-c~J4PRL zh4ZybZ?_+R^icbmPkx3zwA#*|J>4E=^XaP>F1Ek>JO8xZVN#`2Y7MyV{a9TjV1>Rq z$ymCyc(<)F8S&JxcdoqF4jeedxIw56`5iCUIaYL)az}W9(juE(&!0HFEt9h3!ALN7 zNb))3hrf4j*HhY6UAJ3l7Y%ceMleb(i*;d+tVeMCR?ur0*e&$;;QT0aKm*B+;Bi%V z7!ZyQJKR+~8eu-yAaDRKL#Ohq`xH0JrX%q~qucX`9l~OG&8@ZDyg0+>mY-yKPi5;_ z-}7@Q96T3#e&>i4yvB=zTmp{~fO2$0rGul2S|iCp&QnPAFl%sXYzbFG(!tn)vj$J_ zRGZQ_^xP)wW5jq>jVPCjjqfhc=rV@0O5Z^_=Ys<$8%3dPeq%ncqksn*#Y4J;AuqNK z-xzAbFJ9bPx}CCm%rqXXPmgZ?0gIcUw=V;*aLiM*I4G%lGVF)%W;OZTd{ms+`$arE z;;nJKZTt&blLI;(xP@b=#X(*fy{JK^x6=D8>F1w(1jYnyE*~^)t*+YHS~0q53)@H| z&$dt&oGjT+`ozj80^jgXW2ihy;%gfTP}t%~mgMtjH)HfQHpcN((SUeyecd{EODB+B zVVS$jpiend+Q}yXImMBV8Yn9mfR^T%Nr+BhV~d%q*1no@ue|h$j}`;Xn984NvHKyn1Fo`zVUi^`CxLQ;)_o5 zi~2N3{c+>Q&8#qaEuZb1KGVn>^VEu2Hf6H1Bzu+Pk~^UcMh6tw7MmFPYxJkv7=X*X za7;dWG7X4OH@tu^mc^oECC2tL#KF*snh^Tz9(1-PW4R6MfOc?Kjsy@{gYU)^>!~$m zQHeIoghtrlEcI%9vZ#SK)wKapsY$GpYvJt&7E;pAojSov%VfKT z5jja4j&jyIP}}1Vogoy17XSgru!T!+z0+QK?G5%a9B)@?uW`?`CO!VhM`KXC!k__s zi2mzJmMfVjPo0XqDMKfZ9?naGw$QEeNFE;K1d-P-T)@$CuswACY+C|`lbB;CjuPr{ zr5!nblD!4jBK7x}tgT{@Kl1RSZ9kiIFI>FDOKMKUlYjNf^>zY-(0u~x>_y<%j;;)O zq^2*+Ip7MPvP~PgGUK}*Zldp3!RMLtC-F!>je)z~zIl%Gel{Hl_T}Y6yt#OD;kC$)e2lqz&(<7 z8vK-rN!srotAno*Qu3jPPPY?>59AXj^XD?{^XB!tZH5Wz1RiPux{{>)x`Lsz!umX6 zP`wU?O$eVbEgFE?(q@4Wmn6S4D5!jDrYV=XWr=h#S-@+yxWv?Rvju0+#i-ka1__2j+x z|3CiPMZ>%XH#?zN{~+GtXIn@XwC$xm4*W|wP?FTz`O_!c8D{vCC_Uqh9q1g$l2n3R z_odVN`)1JE_thO84_TMQ7J$8$db@QJ-*;VBq2EpS>sjZsWFw{ zuZCtVhs$U3Q?Ihac+P9B3VW2kDL|#V5kh2U>RN`uplPf)Q)}+_7UXUZtFeZ>| z4I4xOxETJ>V4Lwb6JB}bWl^Q03I zpi>ru6=N(Ls=*;K?BN^rfScS9JNu`fo|I{+stJuW)`+x<+{-D;`PcVFKoLWSRaek9}ii z_2Zi4t~zO8VlYkUhlZ4=@c5c{!$t1mWSFGw$622E#7Cb>_{bz}xXPsF8V2_puU%?K zj~rt%I?|pxe=dAoply$%1ApezAH#z_-Clm}Vq0gjcj@hS+hdPCoV_74)N@jY3CEXu zWS_KtE=NB-Goo@{5%o@xuMV6I|RPcVVF4h^@^AxBQ0 zYKQ5^4<9?&e(w*z*)HOh_x$f87?JKD&>Q^4&wi}kp?-~OP6Fk`kw0wz{F~n+RO2Xk z9%>^vXP)3#!{?rSD12Dq=*p+g5;tym-V`T-;l;jyhkXVQy8PC$q65bjD)%60lwaq_ zK^+rQ=-;>QE--007m?bejaFQ3pq(f2iZ6iwU;Fbv*`9j%bdD)}@w>0LOE>1)3HbF9 zUZD14Pn>Ii{Z~)6|NgiCMf(;Tu<1DPn6k*oGM?&ZKXg8mk4f;_H#Xfav5E82<+s}m z9{HV>%la?yo6~cDW zFjqCBVg}^7_#p&UfNGf^e)2Hr7wvW z8bL$7JcJTI&`%wCOB|Muv>Uo(%=RLRRhkw!6++M2Q55+kgSv*m60~~^oJYdR4~>o( z158vxeE7;G)i@U*Q_&kEh59slc%}!@jZl`!v5efjeaAR`J=%tO7&}V)V8m40U^K%c zuZ7`G|3eItXHFh#?_7SjeUq1DcqTiDhkxQ!#*TObmZTx5u@(l5Fkk@-^y39IzhNzP z55pK9i)eHynIbv<=32Z~SY6p*fR(4l1JANleVezc+TOnO$UOxv`x|>N9ye~Vxe$gd zGf*!vplfJ*9HC3<;)l(a=S~#-#~t`bOg;vxM!kcjFA>q;SFYe-_-p$*@Z+b-(QJ`> zJZ9b(E=`2d#A*r`8kQj|6RW{!$3RsMeG{g{j*-`b8V^oh6K;Zh$a{}eT*1IK?%Y0A zo1S@tCC0EkDZUUN?ll-nTb(*Y8$f&YN#xgA6{-5Ad7%|K*O<;V2Bzh03y%cU(Dr2| zTey5jG#F?NCpV#yypeAV4n2O0nc!epVeB3`dWslXUOu%S1MRk_MX<`m=C@Tmks8#V z-S0%#FeWE$<1~WaJ>%u4HQX^KH-?i`G)!F)5NAI&LE_Xc%d-lVf{%LMi6LJk93m4m zuysRY@t(d&xlwqm@$6eIvoSfa@mgcF7-Q|G*;9BS_2hc`&ju4Wd4Kx!X$(Ey*7(-j zkvBI z#iqfjcIVo=OcsygL7yT-VybL=PgXgiMK z{>tlb5=uDHu3mgQG0u-Xaz5`Ac=2ESK|68!RJ`Tt&HWg-1{G;!9%o|x`qi7695_)o zL}i}I(|2F_T0$L86OLtE_)QGhbLSr>q-m~Q#8ayrOyJG;tnk<0e1mp}rx5iL#{!-` zb2k0((mU_8lax19WRg|21$a1xLHW?LPqlyR=Z>{+ymf^$w{M`=Cfi$=F1A-*d8K{f z<4?BdIGsc}zRZN;0IlmvTs_IO2j*~}3ra~ea7xYGZ{{Y6TlLwRf?i|MQad`gv)m!b~{PZ(1$}cnN z_;3EUH?Ly1}VQaNUzx+}!!0b0^y`eDXu--!EN!w|(o)xAj&qsX3bo!#QI6U14w~ z&yXYE7&=coypF7W_pQrpFul_Lqd)gk?bFYGq&++{*Z$V8|8{%!(es4w9cWwN@X(o4 z?FPC~W89vu?sIZDO8>nK=Mv27PAU`=0llKHbmr z@9p2udAhp8LLga>ZT`NI)g=z zimCNbYf4Vjt^}V&Eq0KWNSpc*gp%vV6Rs#U##sxl5J$uxBqv+%G6tD+OQMV`WNqSD z*N?cB5z=Nn;^XUE-AN?+c8n78%XH8)_ykHc)Jf7MToN)gLl*rswK8%ac49nEc=Q#j zR>$H$8|m_(I*4`ug3Z0a3X$6&Z7=bbucY#^9q}HEu z8yj&PJ$fXpY&+=Gbv?H;(S;+{iY9J-pT$VRXS?Vtc2O*Oj3FzC7PrzO_`8O+q|L+U z)IBie8LgNSFZBr*5p=LDoj!9mnl#%+2p0)7&fNI4}RT_W%Gu07*naR0cdK)Z@Sr-MrI5 zX~S7&#M&}52NARkXx;Q}6+^rrJZ7q4>|4w)eU`;}oPnDY(B7Ij8Dx8!NrTflWu*5a**vy7Z~l0v>gqXAeMD^^sSGSul60Mz@)_yh(grJ{#>{3aQqG2U-t2)RqPI7 z3I58rpG}Wp@gIT6|K2yANzc9ddfH9=iw^d%IzN8+5Ss@Lr9XT2#q_Vg`5Xmvk!QEF zUic*jS{*tTR5!^BxXXw{IH) zhfdnJdn`SC@4>kK{F_X2#r^B288ob74Id!fTW=jnU;F+K(icAULuo%&?xE!=$hQN& z<@BF^@iQUgZ-3=GxEzf!z}uJh9o$9!@EmSAGslmozx4RWqb>NIuYNQA=9j;b1{v(S z)3W8=0lIIe#=+o$!}p|V+MombcBIP}S)+Y?7jp#M7-$X`pOZn%@506ALEci_bMHNA z1)Lsu>`}_#Q2Od`v5P5hRsQ&Ef11vq?;}i3NdInMxW&%kIHSI%F!gbzHM zB9W$tb>Ox;z|Fc4jY*uWYY=_m`p;I*S|sL_V+7wI14~g#dhTC690)yVr<-szZx3(QRdzj#%PlUaW{QLb;-X=tSOe!GAH}f~<;TkV<3)e##hm?|tHx#3-X~Jraz$ zepeddE|`*dSjXHgZ|{Q-kj8L2&vJ_Aox8x@!-o!rUdrgQVn>f1j|p?GiyRY%2@?Ye zF!T+~FvRu>y9Ta6=I06HcGg-0DTI(dChkE@EL`kKgY|O^!Z#h3#6fWFpf9RN$G#IQ zphWxR*>mZ!dk&{b1`qr8?oJ08+?qpUyovCQNlfMZ@%&T*md`BC9Fv*mW=s zu>9a8%PH!c>%>e44lz)py!+Mw7^oMz&VXUBWzM%*ti~b%5O?&mZaE0-2Y+SKN*~KF z3bfBNsIhG9Wn~Tr7p}@^xcQ#BbLfpTttBRou5O+rWMwljydwlcCicaVcYxUkIw@k#aZ8pi-9R7uL!?oo{f6f3}$sj5mN`Bd&oGRE?GF$g3_7O>*ZUYTJTyjPLw9E5%uh5nz~o>`YCn1B+q}J4|F4h+ z-bCr6kAIjvWpxy=n*tAIanL@93sB$mLiz(X>yg~Z}{M-CrMXGrIF*ifgJfyCU*QaW>rwY$+{f`QBN zi)YB|KGGj%FiktU4Wdpv?!eqZn0p(%b@BxD1V4Qz>e8+_*!8fDdhIBywe%1t^C6b; z>-OjY)1}qL7>K=oc?{qKyAydr&`~SP=0>okq6RK58W5fed@8p>7Rb}*<>;k ztfn$UJLTZ^KmN;~i6tJt`uywZh081Hum03eri%BrKipLitw z&YyfM{r%tn)AXTTV`|7?q&MkJAw3*A9*s`i>S{w9Jk5QEoryd_8;Ac^Bvke zT8*t+@NOZ^j5zq(tWbBjdR`H@76Om*D(!A26lr~mUd zhsa92CFHpwbjdna9RQ=UdVz#S_Z;tcFe}Hl-H>8my_#OE2T#-?3wlH%gpcG%Pm<)} z^Vxkwztx2x5#ne%^eWuXdIj#t7H$PdhF58NLchMYWgHkMl}B=R20jjlN-SYA*f>Z&i2 zhuG8z`A0{_AyC8dx?TAX9k@4??qDHvqTimqd!y5vq0=+JBv_JvT@?1R62jQnD6ewQ zL#%lv%p^pu&92Wrj^z#zaEF-=_k&ePNY4@zn0M}l(6NdG)+Y6~Q`Zmu$`#f}XBojy zJpLfA2@q%USCW5L;=-%zL3Y$r57QHGcO`X_mpd0qq8v=_f=Fo{KgenmqfE+r@s(G} z55huJ?!M;`+ig2JYYEbJDoE)bu+#OyB?>3`NGK)MS_O5(xpeAy+6`gV`g>XuN5`%u zS-;^!O!S+Fi0K-&|E~R%FD6PuVx5R4$^G8*F9gXSf}Acux^ydWDYzltp6F*Dm4Y(l z${rtk;tAYQM$#Kcj$nD#{bY%i9bCDC0SAMUC<_ceoapw@!w;lU$^*41?hj(Io`OVN z#@akPYh7G{Tpo&ngKMi#K|+t?8||`=Bdk;HAi!lA8DgYUXU>QF!7lW1m8KxhmT=0q zL|9@RYi@VUF9xdM69Z`i=x=R#(Gux;=MLs%&5SLOtizjHzhVbVf6 z{FNbL;i{%9k^@$jquetEDehyXjbm7yM=1NI*PqTaFd1Vt6m0M5p@$w%+abZL#Cux5 zZSLt#vJIba37=NaVOHEYe`%VPOiofK@CWDqC00jaxB@Z8*^0bgqZ)O|xt(TqQ&!ifY2&2=}q2DJPyhJB);D- zy!l4@%|H4>cQL>au$Vr5;86N||K86dgoei25q{N_ zFI#Rq@ceo_ljId8g(W+vJ{yFS?k4JyZ6$F55x)yCS-he z%)YkO;;pqYmizJ=9bUG|OTZBfxvWQ?I{>hAS57+$t!Z`$etbr?<0#KS43tIeqI09C zlt2vA8M>RI1TahlIrHtu1O#)Ea6?#u zv>++F{^eRk_p*Y7ZU$#?8*t*;(YM|TYw85#cpE;Le)Nx?g$ORP3*J7$-^Xj-D@;(k zZ}5Ti118d$XQt`IKFi4rum9)i)OWFq-wb5(mDjLDLXHm{Jd_T`tA1P)Fb@yz+{K_| zHeDdyF?7B6{=<+OmN)~GfnnZqkf&w7#3lSs-M#QFDuf@-=_dC(4kSK7#+gZxAjp0MRP%J$KMl&P=?pLW7Z$O{WQe zyl)ksXO>+fFI*kQNw(^@0>jUp<|>5S32wNKU_k|J()NuO;m^3ikHk3-pe3l=wI-AI zr_WuE`kUJX-u5tY&1d6{1y5)-jSZO~!AsQr6)g7FRb3Q@9Y7FP3w~@V501m=Einz> zbjr8bxTK{6DA#?DK^fSquNAbrh@;lp9!$c047Mx-6Syi(k*~hdi&b<;)cnjJ z2_l1f+A5b^{K|J;On>dOA4{M5$iv}J{wXHD+7^EM<)i7_M^DrC?n)nG(Eigu`~;Xx zrVl@OUwV_l)OU`YOb01n4&u!tF_@(+7`MkVO3b0RdF7iF_0|AlTWXtSdDqRzwc97j z-(uj10U>X6+=t*EeEI+7pM5Jm{@~$sKg$#T`oH@N=?kCyc(}~C%;+O5Ca@bE#Q00! z{C?^m9cM+dfq1L%KYr;O7%p&~g1|p?_uWyav~K_B|ISa~!%luuuO7MQuJjLn`HMVl zzhNt@fu}zmKYKC#!{7V!m}LDT?a)0thGLn5J_$HA6^YX|nLybU;pFaO{8+*7g2 zjq;Q-j!?JBk?UP##s-O->*a=-)#A|_G1ms5Y?6gIl)LCe&B<*}zGwxkM@8VBB49^l zh5j^Fjz9W7M1jV}#@GhYhR5<0t7y={1ZgGW=-lWyeNMk-T_1vMQuMScmIfj^6UwX| zx5ZMI662-2A&i}lvHQGn8l;D{fCs{&qs7eEqLN%YE0c1#i$w~jaM8LYT|i;J(#?LMw#4HeDM=d3EgD1(i3P9j(hwp1w4d=s7}1NqH}w`2ZVVXaRoX=vyI^9qna& zR<-!5&XUB66CjMOvTeU4A4f^kw-j3UO!GVzSBcCZ%Q#5PE}X}5Oh;(uPL<{nx z59@)0J0m}cj?NrEZd@7e<`ur%%b&ufs2V)xybU&qSrdi%I#V5xHDh!eQ?Op>Qg9Q^JNUI|%!^I$o&e2KSf{yS(=7T*p; zzM-pS&~h>hAzEfa;BKt`^uUE#z@uNN;}h;lqk=WU)| zxO_1NliMH@C(-jHR!&`}V$vyf!hBGMu=H9MEhrq4M|XL=zy>cCV%<=#4M zoe-Z~fATJ+){z+4^Gv*T;u*`QgJ|<{0qf)fZ$7Ajlwa3S*ENe+EQiGAiZ3yD!^Q&H zY;$^=_DOoJ>&m_Z4V{vm>E7=Pr zc*e)jlINuHwk>q+h@ll;YrQ&qkd=gWdw57a@@YAD?!g>yXLy={rEgW3u+5T{3k(=X z81U&j(gQo+hPRP(G%OcdU@2nZP7}+-5{EiEZ^ZbJh{QX*A#xDqToZJr9WXz<9I<(0`TQu8U)`@_hIaOAFT5IMkYu=KJA&~-QPI`C z1r$vfEi=|H=WB?y<<9v%^Aogz=wo{=u7d60#KSi$R~Rg_oP5Y{#Q)WA{x9io{^XBi z*?$6(em|l7u;t1^~>K$XK1rV z$b08oSZ-Wu(Xxly=619%Bcr$?A=@0rjOFFa0v7eceBH$cMxT2Mw-*laGy89+Lp;mT zE&KZQMxm&mCFIS2w?@pNNFhyW05*#jg}O`0&G+l?iomrHu(9*FhZQ>h(nlUj|L?b+ z=T#%GaxqVj(J0vPyCQ(E;jR5PlG#r@cSu3rjiZUDl+jE$o6FR{n6%jAl4V-hvIW|X zI$S&K&}mFT+Z;K@8GpJg%nT`mdQ-^g`aS(E@Mu)CNrAYk5$3en@!6^M;w)5E&lmW_VOmR9G% z{_IPy+kpgE%za%xvN~c$NBPhdVIC5H7Hh72?kKPHM;X|;bepSY`1ZlFVi&0eGIdzx zgIQv|xLyqCkc*tX=pbIoXBqKLf-VUXwsVi46nGVY}KSB)RbD(9sTCa`WavQD{ zjtDs{0}etNWkg+d)3Guzbh2aQ8<^O(Stg9vHS(RHEIIeh<1+UgK;*jU8#Np<2-mxs zNq9^~Z_0v%F52QK2RSdSKY7_bSGJ*|SZ2sWHv@`#8Tz3kX8Q3a3{@oj z&a)U`0Ac*TanYjwhrAKmj=|%j58sbf{0ibC+sJoUg0_E$5#ovDz*O=$Nm;UVe zXM_0nGO(Lu!mQ;?KlmZa*gyD#KT2Qv&U5J=Ecgfa?!ldEF1>O5Ogf1R(EYY1^C&)cIfkAN~n%ZrHSmw^Xz;q;L7O&sn4&!HvlBW4++5 z6-hLPrN#6c|Lk9-hjxxI7iE7sK)>dyN5@$1@Xd=%b~Yv~{I0j|a4$ArqwLv6#xkkQ zf6)+NxpH8v{$#xcLtH_#EgB@a6LfGVxCD210wgfFTkyf%Jva;yEI0%h+}&kx2<|Yr z6M_VJ+;`r)_k8F6fbOoncU5)m?p15mp0JGoRj8v=wmD*=ASo~J!i zm?s-)(7D@DBB#4jTjrCJI;3!^83 zS2Y%dz1}r$+#W$#yaT_bS!$~dRHYwszVC}hheQuZz>#B;pNo$4tIrPBp(g>r3a&cP z6aPE$?*xx%E|NeR)!LykdHIU2F&szbf#PgE;&6!`c)XObC}G@5xJ--Trjdj33KE5^ zg@5+2zL%(%=N9Rue8mu{4J1EgBgqeRVk5s;MzRA6+{WSPw%y`f0CyT)?)01;dxoC$ zI;K}MuE05C|1(S>b-$h?5qHnTf}>dGUcK5BnN}`8*-~dym)Tq=vXPc)QO}SO?94?* zURLK+7Z~~pNA2J-FP)Vxeu}J+30!n9e0LhOA0!oo)XWfo9=eE%PiTJ4rXVyGE&s-2 zFt4FD1LbzyNURU8cT$?<-kKge1TY|HCEG>Y;#mISVigCR(isGh0gu2TtLTWm{~$Fq zlHY?lZy86_Zwzo?pGe55OpbJn!{XXXJ7ie6VuDG!f5+fa9R2Ix1c$4jbW(Z4=LE?c zpu~0j)fqY(ek6mf*Bm;sF8c_{05=fd3o>99)KiqzGsot8r{3=Tz=*$Q@QA3nXKN90p<`r2+^;~nadH|cQkjK@+C59tcTgRzs&%=aHd$n z(SYC8n?b*s&Dqu0GYBV|Kz#ucisX2_LO*bZBezt;f=Y9@{Ky5pMx~wucFFASb-Y43 zZC{6__2%S{DhOHm{Z57mSQdXK%HhoYFc~Bv47U!a)|QuY+KgI0*L$O4By_O$M!z># z7b|PgK%#x!U39o-jB0GXQbsnlCys^pY(Y6!NjsQPcrzJH73AnKs$IuLNsC?zmiE6t<{*3Yv){tM_UL@kLJ%tqFUyEgvc_e zP+PfaYxJXhq~tu`IIK(>{L>Lt{9S8jzc~Tdy5-C(4iI2}r##H2W{^Ka{=Kp4tcRZ4 zBO;TI)R=)+jr`G-gLzDyC+ zmBE4H$o!iSm%xsGodY9mVb4RM&0eK)OQ|1!xm&ono>iGq&hZ78P-)kS{5+9uFK)#9 zU(Y_rwowuckLBtwc7lvTgJsDI~da9uCU1FUqN=YcqusQ#G zzF~XPft$!E!Y9j-M`g8w0132gRy+BVgE5+bp`4B{d2!Du3xe)rPjd&+sVDnfwN|XP zZg^n`<4NUtk7hnterdzprfs>(J=5%a^wLzQV#Csh)D$^b2sXzlmeY?P^En= z1O1o*{vEclZ4&Ni25dTh4VUQ22V;9B&U(dc(AUl8JnQJsYy645z3;4cqkI#uZ_wJK zx`M*>JTd3$$rQ(bQ?EE%OhS0{LV0Yb5O^kaBR;p1WyIGDGk@Fn>3=tC7-*>acI_o> zvinQz{LGmI`aHq}%YEyp*`fFYZw%byxooj z^5NyEs4tJ>AW!T^!$yLCJ2L!gmwO%G`2e*)S0ZvzzdqYSK)x%ceBDc_O^$;ESiA7M zl7nsnO|e8r1BtlT!cTN4y$=mg)*G1Ixy1V=XGGkM)7xAaYJ5b8DpIwOA*A%I)@^@) zj_yev63-85d4Akr-!DctcTJ+KPh^1G5qGxD+9#v0lF%x^qhneI@?XrCtKZr3lA2tB zg+&C_6a>L1vOm9^ms0xDb$b2rdK|kmR`uL;N6}(mZx=!w3q?gEY&Jv`F}+- zEy!Tc=fu;~Xp_Daske@4n}#y=}W*Rs=|f-TYmVmZQk03BdW;Wm$mhiBeWc zftTes9X<1t?-z&}d87_zZO*J1A|ENcqBmoqlFR{12g)C)@3~=fjcTwYbe)@#5Hi&` zry=c91{v6z#=F2Tv@&e1Y3>%VF-b$;4l4l7Ry> z-x2VT;dCS3xLT^|wN310$XX#$!7WkIKC9k9d5|}^IE{hH7+V)xGgi%axjx*7sBtm; zHwm7{*4D&(r7c19bs64RM^5Cb<`0Vyb<#6MEAbFvV zJd2WHahAm7(wkzmoIsov!iS-(*TKQz3WR;ACDm0_CnMv$N@!o3x*y^|y6Adell$$s zN%aedVN!tzZa`FX%nsv~UJxDoj?lJ$VC2OopM;$i{&6%3wZ5C-eS!U@Jr>GI6+ryea-#82{3sIw$owYt%+Rv6^EfP%F1k4Av$x zr9?MG5)qVZ>Va>GDql4+U`=ujv^ev9zIDL&=`o86W-CT{(Cv&6#Z{}lR&5Z(YM4RH z!@UlQ{tGD!7asksJ(pIc%h=GgkHJaU$Ju_9 z>jgzP@La{(C8;Cf)93*)S*uJ1^mDFt=M$Dtn_cG6O;G-+6g4_!H0-*Z;1i+?HG4FLaS|B!qo?hX6gH_^;Teu%<=?BaMbC=PB>(D1{wcG8*pH(& z#60aDF;*izPJbg~J7Mhi1pSEwQgEz(_T#UirAvTUf%1A1>rUd*Rg(?K<2;~ z8EXeJKe}waWZWNrc4F;p6o&Qj>9dv1P@zZO<444MzOHYA{q469e2?z~x?A_d^T3Ct- zFWOI$RdlDpPo}NB5yOHp8r$y#aP;a~}^ler{bzHqv zMd@PnTFh%y#e57iMaaelfrPga>4*_ZPKvXJwn>T|J6i--zrQMb#xMEBJTZWKvIdy) z_$ZM0k-B@qbKm7t$B|MD2+g@aZI5%DM8c+?u8+hNer4!u$I&8@XYXZ1*9-D@c%SG! z_i;ST?_BUu^SQ{AY&UD4;3NVEzW5>Oa!*|q+TQIVpSiXT-4kkFlMYJgMveO!<1+0S zZ$+%O=?CB>9rU!;!m|{*=)X)<8(mwD{PqLBA9Uu!o_CbOCAd|=+emQtB+%h&&M1{5 zK?uA7e4n9ON%m)b8s~S}DGN1qr9Wmx1UBQd?JQ5sc%wVw9UOl-|F;Fc-s8HCDmwk~QDoTdI24dUjfRDj9 zVKbcn=w|+N+;s>yV+n#IIVCZvwHqUe;P?qbyj$Ba-;bAXeLdSKvb5Qr-Su)Cm?HKx zQn>vBI^0?>f;+NG2r+Udr#QBR<=!0KNAo*Nn*LlChqv8SrD%_34u$Vy-#G`7+yVGM zyVjLjx^27>?v|Me7p<`zv9b$dpROmpJSK?2m9z#%fj%U!2Ly5#tOv$oJ?K4HBEd*o z0$^5?dbIoV_}rs!=lanFd4M7s1B3NS(7azc-!o^M?Xv;fM9U-bHENH^{FtB5x!8$m_xFF~NpDbJxmLRP2?oB{?`Uk+mH*h?H)DBlT{}NT%sHbvirRdJ;!N$-Qsz^d6Do4EqI`>Xi zT9Sbj=OISjD{o}FTImYysS(<-Ztu~F#GHTI@{n^?%k#?J9WhgW;!9)(Mx(4u~Ldgi2 zPEmoM2o<@>YJ=x;#R|Edgt6v5=9h*j_}^r0(#1=%RXMLb^3jmmBYTV>Z#Lf5j76Yu$OzO_6U(!mC^6HgS zEKfYdSno6h0Q3c-n?DmX2WIMWt8iVfOHQKj!j%9#;lCu_+k7}eGgN1RUknXOTSo&W zp4A85#)^Yqwl4zc8ERTw3&_EdU<<&vS8i}~cc0Kt*=V&Oq}3BQgfC@eLjn#Naf?G6 z$t({jC-iZJ^1!;Mzs7UXz}!X}VMT@6>h9{R73#f2T{M7wb+|0o&bJpCf-ALih5qYd zHh{4ubDNdaH{@TAZfYK&857cA$~kDH^-R!`ExvjK$vYDR+(3gG7bl7>e#LNKsYEtB zjXQxKKIDjzSmRhd^jmbDrm%fK7YY%o5To=I8{wtFm2X{29#t-*COCtMa~&hxgFmC} zO2RGtR(peJL0K%{2uKcelhTyC`Y_*US9yi8^0fG0ZMVO`@7C^u_saCC2J(oVr;i`9 zUwS=i^i&T16c*9mv^vXEiNTn}#wBxU3B$*(?nUJyMPe<--IL;0VT3tr#pq*(tON{- zv)zA719xtY?2P}-{pqB23a@6;(`s5Hrl;H0Ez&ov#Ny%0IwkgG7fP(D<|_1a700vR#vQYQp7{_`$f9PPS78=K4jTYP z_|?Lt!g5>slaWz#Jtxf8F%$cEv&r@~9oxje*JvHFhJMORyNt4A0D>KkiDBby`<*ok zS`d1+A@iK%5th)lqb?davL61`f^y4+ju@|UPdfz1*_dABDIemgPULf}Kr3VnWNZqP zoj19mv$Vw!H_ZAm%w;kmeg3-N#JcWhL7X&t9WF7C@vQrC@-0KbafqDh9R`!B<=EZE zOH_ODUgS8JDnq!PXG~S%Bbik_t+^Ha1Wb!;CjoO^G66~QNyr1K&;|#hfO(a7ysvZ_ zk`b+WEHRe-eC&(fyG%#vA`_w7Zd#Ig<}jpNl+5vo7JERiATAKVmV#x_puG3By20MA zksnT@7NZbwfb~~|EDBk1Uyl;j=SQ>9a-M~Sd^Yw?^wKw8NU&c%v%XSSypZTYrNq7Y zoz6}&-B5DHz_(W2V%7uhd7`il0fn$jZ-@$pi~|RadQ{4vblZCJ#F!+6&liH#Cjahp zZ6XWKwVLZCC7*vhN$^8|;BzuM%eq`MeoXYNKe{iEweN7(@wpx?op%3ZTkP@hmA{kQ z*q(@-v!&{Jx!&4q^_?lB13#84v)X#YJoEvrI@?&@D-^nq{7rpI1mct~iIO`7A6|AG@r3Y#G`!{J`7a zQCmY|7?jw4ozy;cTp$CPjjvrMdATYa;gS^QoP41PJa}V;KHq3&slhmn{^ub!UQvY& zP4nDQ-|Jo2c0B!NHy~0;q>)L$i>}|r7iA?|{)T^Gz-1yI4~_g=+I?Fup7wH;-h~HY z+x@muo5ljUPxcj`lKKH(n+Xy* zn8Z0oEy;}q;1njUDmJyfrG{-;0g>tFd2=N*<~L{HBYW*XJU78Lg>oA0vcCOPhl$MC z0v#SQ_^wV4q7P?W*r&N*q0=&B^YppqFr0L}(51sU^2#8I(0c`CZTs={t2y4rn5C=l z@tCv3^+UYEs6r1~_G@BBCs9|?p^kup11wKST8A7`iw7x7gK0!xvydm1nf)-EuGIas zmSO+LU3W`aq+3nz?!K7u=*Q?FVzy~i83=!1kFFU8P2I&0j(| z2l-atP9Z?i0YEoJSrxN>CcHxvxYRQFr!^jx$W6#5J64yNkqP%yY|om@R>1Zom$pC7hd;Ib9}3_9P4m1(686^lHtkPsL)c5Vy?_ACeo*?t zG*#j6R+mkhzG+s{;jy*DaWwlp0(z4JT}(>y`VIx{4Rk^8Au$>hTWzyQ19+ze_U7+X z4^_G07M_wX6+vc+Lr%)wwz_<8)QtC5@MlLir7gKojrmIy<=o4&WBvd@-mS6bpW{0w(S0sb_jq$O<>ofM$3(dJ;7i<30#>#+cFrKPNu;rX~z-V1XZwtmR z%dXVT8Qc@TqEAoPY;0f+%Mk<;WJ;q?T{SX1RF}C3pGQjo+Zi4zW<~b$4;DOB2fw1c zZE))p);=xl!4+08)M7H6Q)0+0Q@cA0>~@$F*=qgub9JZCQNDUZ0u@6|nVS=){py3J z9+KOe%={@V*6*EB$(H1@c8n_bFzKmWxEBfqD?Aj&>*U0KxfM_sh(8%ZD#qu6W z@9H1Mxu;+{wR~m)RM>7fP4tgJvz0QWQuOKhf$jIpFYp}*>>e4g_~YgGsEHanfHE;xQ~Vu%dGP;XW( zhMig{{Cn*i{%pCKMjl6JQFfB$qm8Lc!B5iyYWdvHf(>8f8+ovVBqr%&W~w5O_Yuft zr2mFI;pl-5vc7-ka&i6uatcsjB|KDt*Reo*!1!hCqPLO!ARqVo?L z3tH_L5}BC;$=LfGy2wuel2pLuf<+D^Hl2I;9`O&>9cO=!bW4kVY$#zKmr+{#dDBa+ zqx(6FJ)@_hjazYIn9`3y<=-?M3hK`T(y^O~;lsYEQ%v2aito{HhU-4u)rJ4QzL+d( zWxdqlOv6|c#q1D__L23kDYF8-2dnDVumTq18lLzn7yQwk*r96)LP%W2_N=u1XucR2 zm=5yThS=FQ>B!$vqzVMpF`a(bFzE$**3} zUv<`fHuGav-n$xN%O7pYp^j#`X}!f>nOdpx$7J&#k8LsbTQj10rYJQ~=AkVDzH2$07_WY40(1pH3k)Np3=kn$jQu3QeMP|avF6FE_RHpFHc$}e78&^ldVxEv&CM8*) z%o<1nDz(fh2YzJT;v842>T+RN$=LD>GYk~J8`=It>3>y!dVU0uvLJ{OK02H&J(P2- zq%4bFY2DQ8_R+FF!*xaDwgkYr!s3$W*gE@YziFRhL0huMLLM(CsBkdp8qKix4i^Ld zq90+uJT3p>=zg%rnRlri@c0XD_}D4hkxNU>7`S|iJrlG)G_SqJkc$+qrC6S2kLa@1 zhIxQpCZoo@C5l$un%0)SM)Ee#!R*|N zt}0oTHDb;9O-=pOXt7S(FOd!KY*^`g0@Bg*l#_4#BUu%-Sa$ZA%&R3o3bQ5Bc=_7A ztwoH}R@uS222@$e*&6NE*tC!JjzuWNpKfm!9@RN9K-g8-pH&(0yW0A{1vdgsd9E2Y zI#0KeJ)WIiLsGucs+1#pN^bAkIcdjH;9Ms00^juHW`_%>g>`ET*_b0KMOzMp9RW^gX19K#Wep zw0GbKPbyFhd)v+$9;Cpgj8iA+BSIWAusc`i4&_rKxh5;AVW?ZO_ ziWlwA{QkIDPH$%^jKpas=_0!)QDE^|&&1sbYM<39y-V2pYN?Mj!uKh)4qY=-n>(k^ zvQMrPL<1<5yVS)S|Xp8fq3?apaCu=9bgl3GL19^A`0 zWCKSALS8BCQpQ=#y{mbOZLDjnleO2ASz(7O!|0%m`s3 zN-2Z!U|37fckwM1OPQa!y#6F0axl|)jPwi-*0a!a;O|P~&FGaVv>fI=dr_8fsh27` zZoRpWSRMxa*nGR%OdwWag+=BsCz{RbnANC2(iBkH@)65)T<@f2@iA3?NU`9!)ZXlz z472-reU9hbHFd?5Uf_gX%I|3$CqN;@I$3WlxAskidh?T~BqbNN&2Cg!3vPD5KwmhT z$cJhE>=K8p8AeokZXKN@m5RJ6O9R%`i5rVa_zJ&roysrLOC8Oj4g{7IENz5VySJ+G z=k~`WuABwr4QyY!fAjWfM(?+t{o@uemuJL1rgr(5Wyw9p4zi)+p6Y)NcMwpBY9Hqvuqw2-R_fOWklSwcFRFl@$lz2x6zT5-j)^1J~ zn|i%39kOO>wvr3CSg8gW%k96nN#*a-2;Ivy{3UQ}G?yf>&AFbM1NQKbW_+UgIoB8)9dX{{6$JR8=T_6%IER-Hwk z{G4rD=- z8_){Qc;ImA#l89exd4QyU{oE#e{HET(d%qo);X7d9Jwy*=u%KWG~b}&n-4HEe|{sn zRCl?|=}ue>yvnk}ng$^eGhIV)e6JEk6s0f{jK0*Rj0&e!df%hKE*Ey&^ zycA2^>PJ{saYCZd!KR}1Di2c_yoNg?7UDp@BFTqq*9~YW7uQriZM6d9nnV=M^}N@> zVnfd`=Z-a+#)1Mp%1>BAl-YwATEpIu%>)l+bFNG-dc^Ak_%KpsNRm96SQzC-*N{G9 zT1V&INtC-Nc3C~lsB`D%Bukv|lFwlB8>;b%7SIYGA6<8qqY1{F<`{Kkyt~vHJ*n~4E~V@TMTx}kYi6qxxfRaq2Pr+&5M$h# z_3Z;q!#_Y1+A=B^Y+o#%3;e8-&w3cfa)9!yQV-NdC%;1;*=P6v;zfu=H}%y`F^TlxFx zhE~lkW)!lX_2U^Du4ZZ}KJ=83MSj2xU7F{NEoEC+^}bJy-V;OFUce=KtHM4> z1T3sVgK3CjUrP5-^KXnU71J@JWw*5g511w77R7hY<(*(%>GUXJC8cN`T~GoWmV0ZI z&FN&n;;jO@FjySeI!47I1gS_-ad47Fm$M~1<6Oh5eKiCfi2#ksZ~fy=EC zU#mNj)@>@s`rf)QABihk*M`g|3w&>9i}&8p?653p)GK63)8q8CMFJ?cV`_A{Iyqfx zP+poRun(;Frbx#h1>Z$df6aB;b-(?9uftIT$ZMZmG5z;*eq@ph-#B?I4?M$4DO7<* z*M}+ohaQ|UEW;)|!!(Te)859Ppnh5uL3*4liSpsgNk^#KI^p-lIzmF%01MGp3IPx{ zM%~E=dU<;)hGh6r;1E#2&owcfiMLwESupBPk^sJT?S!p$Iu^2AYqrnVMe^uG1fk92 z?-K0e>XzuOGs5^++2Rju$gPOTXx*>CB&mTNIIXzgT-$HBSV67DDjGy1@`fopY&fRZ zLCT7i(BwY`L#z{eQ#U5EhAIheamtyjGOEjrCsBY7D$gCKWjq~d_``7t{}cU%Gr~^S zS-fp@&Q+m^g<|rk`}=6l+*X!arxt^J#Pp7uQ@nU)Y` zk)#OLRti0$8+j=!k+Gf9y=4(V^#fQf7E)v&qM?rlrA);(d@Cw^@fIwrH3Ge?r2*8v z4Au9H5oK~~O};0$K#Uy;KIr0?db8-o0s8ZluKS0NNqF*fCYC#7t#Ojr&l`&U(Wx7* zb)3tI?zQpBSSy8jY&wo9=H&`2+AdLY**F^P09IDdc8@bO|0+8%JAFv@Hj<2r2Fm^Q z%dZ=5FQ#kF9mktUGWOALKfviW=nlsBlIXZz`sq)_o2}0pav#vWRUUaSlu7c9LX3fv z(dz52`j-9^?o*P>iFLd}`_Z)~!L^Elgjbu!Zd!C>m2EbuGfWb$N(NZpGZK3BmozF* zNQnk|oXy;mA4u?1-^RC#KQrce3yD4_$@k+#1d$jcYzE6~NlDjNn$EaBr#}!mCEq)q z*d%4>OD3LZWvx&El;(ZMYP^-==dWg&cA?207Y7LS6;1%xc1thc4;^=q9?7aBirtzu~atWLPYs z%;|9D!ub}23)sT=*v8&jnwABA$`N*@`IGWRF@~63^DpriruLtFwt_AAEU>W(-@W3c z5GFK2(+PcKT<|*6YZBuF1)PBH;)E+>{YtMev9ns(-pqkd5sQy(ao`I$Di#rU}22e zg`^Fq_0|WHI#j^AuSH>kVv6AyC9sCi6dEz+<%KkPEGLovGF}S^oNh%?jKCXc>oxfy z?;9nZL*JEAKaV1+TV+C$X?yGUWYLzVyeeJ4v{WP*MWYJ|k25;h6-M$`2hrU7 zLB;Vq5Zu0w*gs|M%c6r5y{yQYA7w=I$-<`>yq&-MQRD4?X6+tzp}$1lEW$5Z@Q?eWgWfP&&Nf~pGXCVtNHqYm^Jnw4Vk>>~{5w7RyIoKEMqH*7m=dD?4T zL#8B4qw}ug#oGFqL(h3;;#Xc`(1h@ruFYlnn|c`1Eo7&h@aTUpv_C~eC`9q3)6&Ix zp_H$fn0t>QkU~S87I2z+N8!RRf{zl&Oq{K98d+uiEy7s17BwM%bQntJbRWlb(RNKe ze4qQw?~13yUYvcN^8Y#O>jv`R!r(A2=4lV+wO8Q03i+(JoR|~ptKg0A8VREX-;WpW zC2@n!r&!tFSBti)mcK#h*l?pJ`-B@D`F^O2UQ76?)yrPZXj#uyFshZ}SMq_L;FaZT zrTX79T#nB$am*eXX5?VR9;gWmJkPSG98@_$)M3L4dMzVcmxZ}{a zP;Y$~cWM99P}}Ss7f_Jjn^`r&J#w+kO$pjDnus5_6Dd4&{T<<;^w4A^&bD&4B}c$H zrkVV(y@m?cx|{<^oX53Dg|TrwIU+QfYI?qaAs{xHN<|-{oeceMp&6TEYj#V!ZWQe< zGW_Z1Jlkj-c<2gybFx#{4_eCU6?ihDQB&H1>yfMG=V%*$0tuzjvquv)+adi)i*7c1T))k9` zL$T=X65FA}8V#3OGIv-Ob(;u_=Euaz)!hTQ3raH{ITSNOVn~ZMcJiIXolGD5f^+f zS8bWakxbpbpM1*tmC9jQw`xk@Avd|&J~)f;*w zAS|F(tC9=;jM|#AN!|g>mA1C~Xsd6m?Oc(0L6Z$N=`I5hxf>{#M?%EWM4XQkbDt^Ro(2uDsY z!S(&9w=*iucfb?xE?#hjc3s&ZYxP;cf#`xpy{iAQQhyUE!(@4`KSucJFXuV#m?ZKm zNU>FTdIgd}__|wD`R&wM!o|`NjbBka)NHyQfmQ_UGrbS*)%^gX&a>ZH801%ZP zY$Q-g>EDR;+bUIu{VT$jp4psNjkf}FEJ^D&Vv%xI4unm%c)ZKbR|-N5X{!RPbY}d$5wD@6 zA0uFC_YqQpPLy3++Mm@oizhN&Nps^aRnIW(cn+9Ru<# zKi0+itzaM2b^5G)DQLF!L;n5`aiyn)uYJ+6wi^4-8-A6$Q>!c}X;YQIyHQSgbdL?N zKp3qmF4ev36&l71a1)3C8iIvIvY>7}y8}syA)EdW<+O@)>op74XSXKKV)&!~hM#V- z`XlEZRP)@Ql`?JMufJXo%-y=4NTr%1;H~R@dD)M;?i)0CQJEU%0n_K&Slc6%nR{m{ zA$;Oa)*ga_j)woJ1R6#(ifkvhMFyze-c+d!HAOqAda}!l9NP;P=EU+1^3Ap7u130o{JM1D&3BEtzAyzKc`lGdkL^UDev!?+s>0ZwQ#E+Nl+dmku=JUkjJzKk z^LGdSJ3~cfLa-(B#-nEkpT&_z|8UIHpGqk(*@7--=?uzT-9c{x{BbT!#U-e#1eS zWV-ZFQ#@gGYbGPA&dPp0gV!X`$-$w~PC18EU3O~)#oiI9R9LUAvTJchgxeV@{YEzL zd2RPAHI{yQCGHmA$sL*H;DowK%r}c^HyCe9dwZ;t&Ae-2qKbwl#POkhw_Wb?oJuDr z>(Bl{TeW+v_Ut;@%P)m2bl9>Nj1@G;^Uk<-YmtUk6?B zxAg=4Vy$y0_lq2Z)oi)wi=4i1_jpa&p2HfGn`+=|KhF$2ox4W6oOIr%m;=43r|`wN zN*M!qPva9Ij$TKmXMlGZ9dNI0gBo%*e;;E|Gt{wRBy(E zamK$(F!#hiprVCuc&11r`J=z(!+HEps2hdXeWbeiroK*h5R72sZ|Ys)37q3orK}kQ zC_B5KRo(dHgyMT=2L=$ei|OLFcq1wmYAHFeciDA2iUk9rO`&_u9bWFI>%DDL)mRUc z)g6W+PM%IxY+eV7>m#)KLtSYG>x*quaV9qxY_tyS9+92DirKL}kUuU$ZhT!oEUKIc z?RGA2t%Kb2Htjpsr#y#!t8UIO^>d6*TF+1PMRg#CENhLB^aH*?W2gIxDX06+uNMDn z#w_IKmImrEw!pP%Y5k!;lN?}P=$&vNcVx%R!3KMdC#~9S`?8JGU+=GuwDHZM=UA=H zlg9ik138UZXZ_0sw>i`{~|Kn8>vOj5@(W!8T z+9QIX`cVD969tV9&W>>z!p|e#`j9=G$T40t82XdL$ivEokLG_4{EXZ38^=aO^g~5Q z!}y-x#9lT%dn{oCU7+2-S|gf4aCG$e16wr66Pg zR>6Rw72+TD=%;cO^(-2V-VJi&hHax?iww@KSzNon*_>ksY?L5fvi z|7pi9)#oVEQ+r$PD)y;MXCjyO+_LlOn&1dd2eB4#Qu175Q6d{eQRUEsMYhtBmOomdqZ{yb9j3clRKHrEM*rtThiHdA2QU zzSP52*>WT;TNiJQ3m)jp&H=G{TTpJ&XyGrg2*yZqjTWnd(nJ z^@<d<1^f01*_2gYob(8&Itv6CfeTbFSou-*PM#pRDfy? z;|_9C+JlTRb|nX?TVQuWtOmPn5l8XsdAqX$piBKLmyh}XJNP2RlWG4KhK}dXk zWEKeAbRBi9LadipDB0s&W8dc9bZ~vNj-?Bvziqp*Bt2JI5tW!e8@Zt`p#qx0z9PTmByg69jAH1ldF&_pfD@mj)Vp*a34@Z(KkB zxstA;b7_~aJKv@*ohb5nCQZCjNB1%9ayVS2;hNg1gb?BfrlDGtK!siH@^{2N0C}_oUR56Bh(F-ba8ySWAAJcc(qdQS9-rt|%<{ zYR~mJQ#NNgIEI8hmOk8{A`iT$`In6R&UuwR6tr0W-jZNVh^_93u+Xe$i(NX&JHXXG z&D(&*LKOK|;Qz3GMP7mia~n%+A)BAh^$NDB;_v#ZqlAF%k_W^4^v`2R)9Ii%b>s1@ zq0YIrwrNZzOT9U*(cjdmmiYE*gta59*{biY-=$%9Y`tJ_BC#xoSh#b4+X_;xse7;R zo_;ay8{;~u^8fb5|8h-!co2sGP?~*L&9&=t%&B+-8Yp~w`Us0ib=A4vATeRPL7sc6 z=uQ=44E%Vz{(QkB_{Yd9bjJ&;_u`0Lr6TlhEA++2Q_6GCn{)BLUZc~I()GgPxQlCi z;^|vjMm6Tx7z?(d0A4lbd`yUgoeK}jy!C%P^_m8XLW?tVt{7O`O09HqD?as6ZtmM= zQ-jZrap{Y4HwN=-qk9NpN8Dv>z3uiG>c|75)@zT&vC8WO^MoS7r#7WS@a*v#WhtyZ zQ2h4%pM5}A*8Qd=G1AxdkZK4Zy|aAPBkM1yu3QzvFjdwVkZ|rd2EedP-mvodzmm5j zBB`dBD{EEP{Y`oOJqGxkaqiniS3}2qg7}}b`TAWQYWwTPAv02RPu8MvsJy0=vD5LN z6L>h;Q%>dv>q=DlrFdlov0TFV=bQQEf6Ofu+vn4?iKtMJw(~?)u?b;1&q5C2**+Vd zYxbrubeblCCgyaR-+H~dLwQP=SGYp&KjRM@k(Axz#keNvpf`1BX_<#D+@U$pQmlBR ze|ff3ZT5bGuD7_}c4xkJUR)qGq8T4+ayh{lWbN$B*T${u<{iLn=>0sU^(x(+fpl)7 zX#`>$@qWl(x3P`7S_#>{p?7FYKano&TMtNf zHD@(aC}yvEKxN5)6@ekb7qo}m4Y--_Fg%X~d-+oWI?rG7ez0-bvsGk2eLmv8`3Sv= z?``@~+$yUU15a)BdORZA^<^MNJ?vosPJ5C*NlPUlq0)ep!OiRY?`R+-zF`SYw8T$a zUJy^TcMMk!-8e90nxUJ~o6xVOaY1-~+N<3`Z?nvb)7pGzhbxyl(Hv41AxmRvlOquK z+2FBiwkun)aIO22;?;R#feZlYlV?=|ivePWnN9y?m4}&;G<@h7$=>mEY1f_={TJo@ zzbW^BBcBw%tO}h&(+3oL2IxigGm6cb9tu&E`=6l@J`nTm9j;d}f}0kxm&BeUkdj)H zcxw$K=RcCV**wl#E!>_LW~REN0&XBfX3a!vQ@`=Jha8ExG>>J3<3j$Jo$iwP)c(h?jzZ-PUbP?wPbVBF;>uX8>APQn}#c-OV)AKy=?Zg6PsnAiVW_cSH7^lLhcuQA>|1Xw^IfO0{= zNvOS1YXv;QUCyXK+j4J2fvXt)K48%p%{;`*g~9k*A*#3j<2|sG((1;_5Ck#keNR1K z8=b$V`ltsyw)i)WcF7Yf9sD3l;Lr%3J?bf}^B4C_DnxL$(MrwU@g#?jjuM^BOLFIN zdb}5!dwSehZ?`ZkNq~^#v2b%pgU_u~v>lu1hQ#aF zZ$HUf*hlcwE_k*>q(6;sPo&T0g{MloM z#Z|vg;%5qjUOT=*SxV*f=GYlD+ufJgGf6_;G#CDhxC=sqtQ`0l2>Ro!HH8*?PipxX z;ic%T;WGx>e2X~eiMzKxpOjxdkC-awLmk#O?EzuUwlw2Rlvf4I;vS9^^+Tg7WqrB^ z$yR%xQb%iPvgh;v?asMo;5a1ni%-LB7sFLWDxFX3MPdClY>)Xl@=pk!;IiCSiFV6r z3f{MGQg+Y7>$2M?!ffBIA`21kgvFj_rVd_jHYabkCwijK9#ik&YZT?}w6h)nM|f*7 zJ`bh&B?bh-^9=l_=pfmeGF*VO+YY`t`jY$&1)m ztP}rZxAW64{H-g?BWLDoF0v9J2ernyZKd|akCseGwCBSxWJM3xI&)@oit{U!fM5mI z;aaJcTN{A}N_UOE+q!=nfFUe+<#^lGSA0GciCNnb=#*o8!}@mlX=xF`4+XjvCP(IA z(x0@dVpGAFK9&Y#aKy}qHoIZ^?+dy~O(zZR73N6%;BYQ+_*Z^X0<}T~YTroo)o1!T zDDPE#^DOa64_r6NKM^u-9A?0V!>VN|I{SQmXuT3UgY)Wsz?`I$0c(c*gs=`oBjQ0V z|9}4L%7_#@VcU>yC(~GLUOz6brA_w0{QLu?YaM?+kVrZ_gvxX6QX@R&Om+T=A?G=YWw%Z=8{~T zk=V1-#?Y?n;#oObnc|pfM_6HtdfPF^S3Zs(|WsM?y>d5kZtZ|~P`;`4y_Wv3dAk9sXp{TpM zZj<|(O&I<|-1H&q?$SbXWj@rc+&L_Bvr^ggIKK5h$)nVx$O>Bfe={H&&_+RCwcA&!s<^b3+A>w`klu>JA6oW8KyN-@F+h$ChGp5rI!1_eL(U5|-xfc~W72;S);|EF6UomQD%sN@!=o~8wUem>h?1*jOpqV^ zT&$(=xfUudtC6$vC!WuX-v74U#0|d2y=t5C<6Yy3lG#)xZ>@xq=D76OTg3wOY3Y{u z{0ol$ASI1hpsP-;%fzN&Mk(`KMr$N#@27u#>*P-9WD@P{k~Q9c@JlW8{~1*>)E83* zyoQWwtBA9km7WAaPR&6r9*2PDwh|mUuU^;umD2Y&jQ;0ucldsW-}L%8o>C3?6#>rd z=j*e{UG&=PT69Ic94QJX94VwV`EclEb!`5U1I#B8 zRUUh)v*32N>C;W=oiW%*&C@Wacw(yfK&>1o59vPZzRd<83N|>Q?i$J(pnsp#^%H%;eLsJ zj*xGib|1lI`e~CU*RQXyEdT(T@v*Uj{{H^D@5=h?_wIvjo#^+vx$%u4%hi`5oInO;~(Wf|wF$@C$V98a%PWmwAhbC`p$ zvJrH+vxg-z81#u$ffe5yFs_dEV!o=fz;0h%0a{!ym0O$+0kM}I9cv}2>NC^R4m8I^ z4>l7jT*to)+xP*#5KW7V%}gU%JkH_Qi*pMLgk)Zm*?Jeg)9hQ?O}+T}eBsvFh}4YX zF)|Q^<82L3cDRr4mrTwAWW2!cx68FYq3e>j6_s>6ei7Zl8Eo|vDpkecq2GGH1*?Zg z02|$Ip;GVg^$C$x`OJqizS2ScA7RTLmZaKbutn(#{Gt1OyYkF&RVOy*S&Nmy6)Et7 zN|(fYWzR<>-yU)Z;FYMW(E_nT?Sp48$`=QoF}f83i7rIB)W(RkV%yeHqYzm8jFOL3Gz(0rCdk8(Y* zOEG8Iw-fr+Xd`W9cYQTK`Z&2kUv_3@jR+d z*XmIQ;9{mD`GgA>B(A32p>^|2(aP1{BLESc zn$w4`g?5cMsy4XnpQyeO5FoS(0|>diJU!edeACp{)Rda!zvF|IdVi@_PsdhL%pacJ zbNTwKRmYBjz{-WYR(>TszYh5xKRFGhj|c5V@@+fWVqvCxLV{9K@0Tjcohgw;a20 z;e2IG>pjvS34T19Ay4Qy>lQQH<4iE;ys@TOr?#L5-X>9I-%ItWNI$@LYZ3lNY~Fbr z4D@=pc0>&SFQXOh4$L$9-A9a=m9Hs8tE+v#aFk^%mdR#q2GEnA$2wtQC*gGauabh} zQkEB#zU$j!`iXShQg?bjj4a1rJ;*ciFC`TW{1(N#vZ95EHy^VEpD#q2bL6wyy<72= ze^l(Oj{R8HXQ&Z3UX!M^L`FG2LA)PRrI!S%drrAEKSrnx>vp#KV2&-_GZc8byW?0r zWE>7t62^QPy**=log}y5ZVKMF{W(Tt0+FzTlb(U0kkxzwLa|M6$Sto%2Q$L6{-Qji{~O=>7SCQ5QDPQC z88-&}2xqC;_eBW~%xSx-^S91+TECgEEi;V=pDkK&k@75(lf6_v39aP&{#7cSbUn-yE+w(dQx zl|8-P0t1BJs`4K9J7#_8tMt`YpV~F_)?1y|y`p1L5uZ6uk=@+dhk|Wt+B$R!Fj!X? zZo^GYqlTp9d`68=>M@@XMJl##Xw5SL-pVs=^9PsN+fz8nz&oom9Tspn5Ya9qCU6;a zKW;1WGcWq&l*Wzrh?F%8FL41DhWN1h!f%(G$Ay{VS~|>`LSeKfS&W(&@ez*t`UJ7w z1RdIvs>;s$(k=1`h0E?WTo>lazC%R%i_5>`Mr z>Qw(6oe$2@!NjY<|4(<{d!2htnj3iiT1cWH5RR#j?Ie>rMaq=A8V$TLNr}+q%VcQI zt_FHn#K?jD@WAZI^f?{YgwP1{JS-1z4C!s=X*OuI)ib-;5LPzm1Vz@7Ae>Wj&F9J4)%r9OQ}*k)C;I8Rra|gCfZq}J?xOycP7)Ck|qaOkRYmw@74TMuQ|(6Qo0`F&BgC0Ak2mx(na;##9(4`KG_xd zpT$-kUTe{KNJ*G~PP5V4rLHZyKRa>;Q0sLi0TK!bgWW1+!s z^mxqC8(hLv{Q|4{F^~0D`t8WM#(vr&p;yww-gih?nwZ`!7PiU>J^nD(9K|#$P8s@< z|125w3kL?BjvXOBvr4OW-pndHd3auX@mpoPlPzNkx}Oy3y>!=f4zaZN%;?uT?l2WB zR)T_cdG%70!p719Pim=ON}65!o5IDTBbdPv&#&zyDbYe6A%Z}SIU#Ouuql=n{I+ z{cX-zQqan4ggJfp_+JJ}N#0zf$-IPzGK6RJb}3lCDBf5mx?#evl%(+@Yn34HBab>Q z?j#0O{&amOIc874@VSjE;g51D7ys+h?>@4@B_y=Ug3u?v(ux83ivW_VKuy(TOD58X z*N!4G=)R>Bf@lmY{4|>i+HgobT%HZ(ipT5emou`qYdak-!tBa0sslhL=L!!qke(<5 z!KIW%>cafc_vE9M~%FtE!0988}Zw(1(y@L)BuPTjiU<->sf&~-l9;?TYsq* zFb9DTW|~&JD3B;_d)N2%y>WhTcroXJIgszvqyz({>V5B6 z^dCr~as!_^Ujw5Vy}JiNO#EDggW4ES=4pwM7{Xjb*s;Q@$Fo9;Jfb2RW-Bl6v-mwI zhrzd=G}M+=CBhT83j+w?qw@&_b!0>nXwzpho?tF@@?}IzX{0)VZ-(JgA~vX-Wmv4v z($;)NV4zAd%-vVAt`ZjQ+{cgeL|~xdt3>Qtrd~<_*-WV8EIrlrm;wvYtnFqk=_LOP zeY%i_NbL*GXP?sw8^!4oF?}$>j0xRDP1y!o=>6)SSG-AzABioSS@MudN=obl+iKz% zJ}$~tX8G2Y#%UE=x38llCPR|#Rd#+rDaFuHBWhI01}|0$<42E9q||*hhZTFZ#CRTI zE-^)Sr}b%Ki|%u8|B^ub7Ks1nR5F2s5_R1%`1Df%vy2(z`>#>0&$Otns<1A&VYXM(Qz4DLSD~6x zdkceF_Q!+zY4iAMT3Tf8opYqG!#mwKXnj>mKdCjVTG4Ucvzo#H+3!EQ8tFuw^qcWh z-sb3psP1U$MVwKRjk8&^)=k~knqUWlpq9nn-Jp}4cYpc(QrrcAlyxx1_5qmD0V^b^ zb?;g><8DB>1!qG1!DvQCM+>Yp_&z278HnKgw$VU3fDbCK4h2jX_2Z7K&e8ADAVuT# z3eI2N6K?)$^Mc#}nqjMBE?_WoaPn2^wEs-$=l#qi=0>!lp|J(%oyResnc;`!IyYq8^#NE+=T-6vi`cqM4Q< z@PE3TU%aRx{D7r{h~OYE!VQW*-~eCHeg^{Z;1;T8XfAs69+kCD_4T{472SC%bks$X zH|tKB|M%lf|0m)xZ=NGDMyA{1iNhShnU2Ev)o%8vKgx$(o+b`8;hcyTey z1Qt|#gP*T~AnX@kARIXJI>;*1csQ)U4bR^Ty}LeFU^-Dn>?NZU6atl>;EW z%`hbMOeeDULj-vIbCx|BR+~Dm8=~J_o+m{nU$xoZ-YXm~r0(7H}myydnDY#(tG}V1;ryiW)N!@&V=YIKh z9E_a=tkSn+=R)Ywmd(R$1QXHM9nnS z>Ywo`qjt3 z8Fw9^H07a@Mr2w2ZLv1s_YiA2ZNW*YjwxqUp63*vbwYF+c!T#03k4D^9T~iiUlrzd zOMl$E#|WYK8ewZFE3}soAdFI2Tg$9irk$f)J^C(Ne>_hMMZDq%(;RbKWhlWSZ+n%{ z(guYG?MW;Ti)&1EjDUt@a}SxMa>tHLy&jE5RZ03niWBV~wS+h94$-e}aKUYbynH=b zGe?pDM%KYf9nYA(y)v4GV}0`$mVGcdeV`}av;UklN`f6CjCQp49W$gON>S>qEff=@ z+0%Du5(P6AsbFPWx|AOK503SRg$`nZdOpR47Yx^vZV!=aV2}KSGThazS2)vQWtdv` zadHW{U70uIo$ZLf+Pz=$*(}$5>_Y2+CGz{)v~$rjKM}}t^8wnN0JWB;rZb8kJNALM zNPGtMS9k7)+yBFy_Iu6x;%5$2?(1m9i->>EGK@cGQ3#a^y=!+4|g zg!Kl=AayHo)y~sb7x=t6`>etDl0NLQ$tgyJ1cIs6Y2IJEL5$4u(gXE*7aI#Fq}P~WB6O&Ar)E!IJsvO* zHnNTINXontBFaMzPP#YF1ewwcG7Nu4Moc;J5f0A_u3w;V3Iu&Zsz`-2?qNaJxOtB{ zj)$+`YAW?S17}!m`-*=+BKF8`>pn82lv4yAK5PdvWkXnEdp)GMgBIsWg70Te$mBzN zbqnjutZQ7AZm2#luTED1-xQc)Cq2T4`hO5N1!^N}3I2R80y3Ph32wU)$2d({Hrn48 z&Yt!~vdyP6OxhWu+kSvKuYla6S~J|iq%nsc0O)*vdU@cq{{SY%*!`e)>8!Y_s-#FXD z^S#6-zFqg<7u}IRdz)(kF80RnXM9X(F{Hh?s*@a+COZi|y7D1MdXlMn;}n&>N5Q-4 z_QK{t5-Ayp1&Nu>os!2WI6%o59D~hM@|*ce_8s(<@>(E*R5kDfw24(Lhb$f4)~}$3cibHvH=z+ z11%U90R&`SQ^*lRXH0^~V-*IbjWfU?o*yh6!j^lCJ~iM_N=_kg^_LSFcCY6oLA2a& z=Hdf&(CSFCwqSq z^(2sC;{oxjbipzO+7GehE0-%t&-Fw(AF)R$3}K|kh|y^?>VuPyX&6`E-i)KDxhoUi)Z$_4&cIOao{Q67;SKM_6~%+V^#6|Xff2+Omw26 zBr%lWaL^HfR6%|U8bJBhP;A_MiXI1YFIfZ9p_8v0=%eu*YCnUZ=H6bv9QLc_ zL=d$x&+DEP2o1$ZA75@YKcw0WodmT(VCis+{G!B^oPo7%J7vcpfXgf@q6l<3Lzn~; zyo&;)1|$hKJc!AtxT|9QV*c;}l|@=iSQXcexz&DC`Pf_KlSUEyI#>jb^G;j;Z7=}S zrcjnaP!xA!A7*P(3wIR0+8B|PdT_tu5r`fS2$sXUG|WzXJMyQJ99b0vGrasMb6@Ac zp%@Qf`!#52(`kuEx9JLc@4Q3r$?ynTe%g4+iXEM@og@0vN{St<__@+V6l*z-hQ__c z!gf5W5PxHl`p+pL_xrG3>zTp=cS#p3+hK0!ic?90*js@nQQN+u;&CZJQLz=XEdMh# z>h7%`(Wpq#{sYP^X3;|!*f65&c)ScWqs~Xs?U?x<6G#XV*EJXc0xqS`2A~R{g3qx9 znJa-7%MGW^+#0$Pdl=AMv9!pG(y1flKXyO;EbL{E%*Ajtw{S701jnaO63$l7LR>l#@=lNu(DR{IQmVt?rFJ#-=+;zjCJ+i~XtWBjR-Ja8aCtST-@Ez+>AZYD`AhGx*hjDw2A|kGACh%5e-3)74wEk^AG1ojU9(g zU%#4R;el%C@}3=mm?i59Qr&VpCNdIfD~`EpPR!C;FCx!M2HenK5=lH9f=$&7__55v zc&w}s8ct_m7=rCiiZovPU=L=tUlxpQMb*f;f-^rJuKA6keOyYybb5;OGUJ;Z|>Y%x}+OSnXMy)0z< zdK*=MW6(T`!hyQgo~c%?)~K2#aY9h5shsBfRy+J>C9E&^g+VI7zEfeR>IPn;*yY_b>#V~R#mRHQ49r*k7W zV2t&C15n;eBB3bE(+CR2oRyz+>^StPLfw&+6JIn`mPo6kE0htf^Q$#_edtbVPgtxBi50j*IwMxVW62gC? z9u)M#I2WsBgys$M;|hE^7M|nYa8@A|fqwZot;Q9K8s;!|-glyzsT&HP(f5*P7K->- zta()3iO~0a=IAiNeUem*i81dZ>){LrF()M|8jv7;y%J6TPyP=F@+f`nHI~zoeUz?b zU!>GH@IHhVfE&#=V)9TtU&so8_I^lFy{ih(mhRXnEz>bOG_k%NOnMrBt2 za2eC+EE7-bBQ^lJM1A7H~L&sP*P?*d*gPJRJ1Aj8@Q`LO28#|$RV@mjMm5F z2d+ANl#{gsJ)ig6DmeuRM^OT$+EDTvCVMyHiZ7rKJ_gH)r7bIr4l?fxydggWm_e@v z@kgbJZpx-F&NKGR;dl9^M#6N43ff!J({bFkqaZjJm0}3Ol!@(37yd>g9uUyTCKk-? zK_pa=^WC4#1WnS;E|N2-=NnnBn1cSodHf>|k>_RybCJ$=O%>V9R7bJhF@q=^y9BTc z-CjXPnqBhVUdW(2aK9wxqOne-8Kk_we;(H^tq3Ayir;I_8lyjOkCO21`xQ&NVKEVG zW3KC5*hA-!;@IN6vn#LNFcJoq2VJ7iGnbA?io2cYJqOB5v^QB1E6CHS2iyW0~E zqq&$Mzwy#{GEX?YUwG+lJ5$OwH8URKLC_PKSrr-98OlAs%t1PfJ+ZKQ*(mjk2_aXt zx_Y@L`SxJU_&r!`&p5~5a>xg$0}n5d4nWQwZillGzc)Cr~bk{k%##^%mpoxu2I(p2hc>PA<)V8WpUx5bCbtUOr}VG7FQ`?zrybk-<{ zIx0xtLrJ1Jve1Jjc*i1WMKxNDaO;f^63F3#a(#Qu+2oqn?s3NgSVJ~kX8&{0EMdM^ zahLa3FS2a`xEt;xL!>f_O+|v-sH++`?PIqBJBVPISiv6<2tu@D#TjQd;$ z!NcT6jGp!H$pe((@WV4msI?G%QhYV$AmWBRx@b-OiRI#)xRXx@C%+Tbm5;k1wnEyXqnP+zc?^SkxTtD%O_2X zMj-5Xo!VU?4^}PcTylXsX}b;6?6~9k?lAE=vUg5PwEiwTb0&}0<|bbvDz;G8Acb0m zVw+I*Taxp^>gm>BEUT+V+}Dl|5N(O|qmp}d&8Na@JByUWZ67K}Hj~GngbFh+E_Qy` z`{mIYM!9kx@U=Ht^i@pHb8Crs?<%1~4^ZBO{>8MUuY}HGI#=Qd+rGV8sqtv&n2g1M zpE%wscvAwa@LA{HaPhB|A+2k)}@` zKj3znY&TI7_mi`8eWt6iLG1NHYL=y@?E=PM9TplU=~`&X|5U-91SkuraLdkC`Fg;0 zTfM;}zUXgJR?t@C5L2$(tUVBozg@Yi6&0v&OH%j+gj)gxIu%qO{*#t5I$Io5sr8u! zVFRo?p-lc-^}$ets01R!qF;>{_jQ_ClEnQnGXVqyZa0i z7%mHKni9EVcAHy0jsk2_JKeyi*f7bAL{ZR`x=Tbt0k{C6j7*&f6c>Y@0nhu|Yx(YcrO&dxVY%Z;lZ04`91#Y2)V1Dl2K@Mx^7T+5)#P)XsleYsS#$WHmw z)Zv_pVjQMHCN00aQ;LAsW0OQtpWf)w(qAI}y8!KHj3kwC6$Fb3-OCB&Sc`KpqkaZ_ zCBp-sB3EE8Snki=n}q+5MekzG@x{q3oH!USGrJJ3l4aiagV|TpQgIV%9zuF}BlSHG zRwoPkPLEcRvTeP;k~$eEp^7Q?>aIClB{?J(jAzU_p6ii3N#2oM~0R#8A=(P;c&WADzW~21)}Irn&t+9;M=7gT=3oKF2*q z)r_r&hxs$Lw89Mr6V1(7c#&X7mC$W7M_}oW>TNZ*NnpgcR846MIBXqW&B(%<-5h+H zxXWCno}W^VHWg1y(?BK|Sdae->loCsA%-2A{v+#KG{p zxL)jewKLp&K+89;g{gf<{F} z{SoLFYfOqWrV|n%nQO|Ehr^#GX+rYLpv~2e+=*IQoX5X3i77DMa9Gw#`Ux&PDZ#H7 zHR1E{KwWej<nJuhHmUUvufn3DPE}P^ybF-okedT3OCa-K`vKtm z^23DEb&p*f9c!%@zpHWBu29L#dlWV_NDM@Fs~$+n4Aq}ZC$itP;}wzv#jj+~6?2!H zpC0_1SB*LSwZ}%;@WQ^uCa=XgAUuhE=IPXgKY9L&JOvqwsA^`WCI}o6Pre7l2-de>z{J`A77>jAXok`Pj|cu?^~o?7Q=jzTY;QB z0eUtz^l+|}JErQ&*`riJQGHL_9~6l6d6kz8^Vwg>!B8BbAT^RjOqyakzk*L_jYg%I zH(I6JZp zC+FL|>TCBMc)*4expk7GdwiY$Ude4bC8D&!kVL`1%Np-0|07fZ_lJ7<>j32+*_rjQy!d3h)a1ifUR%lQ zu!rL)(RzM~oU=(EWtemo@B#ucOF-+X1m((u9>TmEM%X|Hdqq&mvwr=!d7>@f14$p4(pZxl2bdX847e7vWTKlW`6D@aw1fXDHvGq`-**dq z1aA{wsZl@8q|$&SS0tOuWC9H#-Lv-^OzrA}q-3;JkTa*h8yq*z7VYQwtIhvb*;pb! zls2|pm#~J;00dh#$fPR2$!>%8m=5dDCO1J( ziNsTkD(R-~oY;@k*HkSDBiyw@?!~^iNa6HAs?3WsiH5&RuYY!3gyh3nI;=*#@pg3o z-mhk4N|4^(6~2=Gw(>}1Ex^nk*-d+7V6~2FG9E*N(O)%;|8A?`2gd`BFUDU#&)2Nz zcFbHgtTeXcyr*VnH?)(oBi<5uqv4o5Z=Z78NtXYXNdI&mp+S<^{dza=GKS?g)-;=y zctH%2bo;*dyh%YbNl_5Qa9wLUt58>bUZjQ4){7{@tEp@`>EDwb4GEs8;?9!&C6gsO zVf_7C*DNhUM;Cn0bI`BKu+P)A{nUZ=v;7sT>Qg*6((ZKQostAm?vyNhl?orxHPIAKpLG;tFDwB8L9|2eLvg?EnA( literal 0 HcmV?d00001 diff --git a/blog/images/20230722-search.png b/blog/images/20230722-search.png new file mode 100755 index 0000000000000000000000000000000000000000..605d8cf51a74b937422a9269181049301a1965e1 GIT binary patch literal 158251 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

SimpleX Chat vision and funding:

+

SimpleX Chat vision and funding:

  • why is it a commercial company?
  • @@ -6,7 +6,7 @@
  • what is next?
-

v5.0 is released:

+

v5.0 is released:

  • send videos and files up to 1gb
  • diff --git a/website/src/_includes/blog_previews/20230523.html b/website/src/_includes/blog_previews/20230523.html index ccafa44cc8..7223501f11 100644 --- a/website/src/_includes/blog_previews/20230523.html +++ b/website/src/_includes/blog_previews/20230523.html @@ -1,4 +1,4 @@ -

    v5.1 is released:

    +

    v5.1 is released:

    • message reactions 🚀
    • diff --git a/website/src/_includes/blog_previews/20230722.html b/website/src/_includes/blog_previews/20230722.html new file mode 100644 index 0000000000..de6944817b --- /dev/null +++ b/website/src/_includes/blog_previews/20230722.html @@ -0,0 +1,29 @@ +

      v5.2 is released:

      + +
        +
      • message delivery receipts! ✅
      • +
      • filter favorite and unread chats
      • +
      • better disappearing messages
      • +
      • prohibit message reactions
      • +
      • more usable groups:

        +
          +
        • view full original replied message in info.
        • +
        • share your address via your chat profile
        • +
        • search new and existing members
        • +
        +
      • +
      • stability improvements:

        +
          +
        • keep your connections working after restoring from backup.
        • +
        • restart app (Android) or reconnect servers (iOS)
        • +
        • more stable connections and message delivery
        • +
        +
      • +
      + +

      Platform evolution:

      + +
        +
      • problems of public groups and their future
      • +
      • what about read receipts?
      • +
      From 26a233ab1a04ad85e358a8578cb46ba314e36ac1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:35:29 +0300 Subject: [PATCH 17/18] desktop: adapted UI (#2755) * desktop: adapted UI * more changes * divider fix * do not close screens on non-desktop in terminal view * background click to close views and small changes * dark theme detection on supported OSes * fix text color after theme change * placement of desktop text field * marked as @Composable * padding of text view * window sizes * screen layout --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/multiplatform/build.gradle.kts | 1 + apps/multiplatform/common/build.gradle.kts | 1 + .../common/platform/Resources.android.kt | 2 + .../simplex/common/ui/theme/Theme.android.kt | 6 + .../views/chat/item/CIImageView.android.kt | 2 +- .../views/usersettings/Appearance.android.kt | 2 +- .../kotlin/chat/simplex/common/App.kt | 221 +++++++++++++----- .../kotlin/chat/simplex/common/AppLock.kt | 4 +- .../chat/simplex/common/model/ChatModel.kt | 2 +- .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../other/wheel-picker/FWheelPickerDefault.kt | 2 +- .../chat/simplex/common/platform/AppCommon.kt | 3 + .../chat/simplex/common/platform/Resources.kt | 2 + .../chat/simplex/common/ui/theme/Theme.kt | 20 +- .../chat/simplex/common/views/TerminalView.kt | 8 +- .../simplex/common/views/chat/ChatInfoView.kt | 4 +- .../simplex/common/views/chat/ChatView.kt | 23 +- .../simplex/common/views/chat/ComposeView.kt | 8 +- .../simplex/common/views/chat/SendMsgView.kt | 2 +- .../common/views/chat/VerifyCodeView.kt | 2 +- .../views/chat/group/AddGroupMembersView.kt | 2 +- .../views/chat/group/GroupChatInfoView.kt | 12 +- .../views/chat/group/GroupMemberInfoView.kt | 2 +- .../common/views/chat/item/CIImageView.kt | 11 +- .../views/chat/item/CIInvalidJSONView.kt | 6 +- .../common/views/chat/item/CIVIdeoView.kt | 8 +- .../views/chatlist/ChatListNavLinkView.kt | 12 +- .../common/views/chatlist/ChatListView.kt | 26 +-- .../common/views/chatlist/ShareListView.kt | 18 +- .../common/views/chatlist/UserPicker.kt | 6 + .../common/views/database/ChatArchiveView.kt | 2 +- .../common/views/database/DatabaseView.kt | 4 +- .../common/views/helpers/CloseSheetBar.kt | 8 +- .../views/helpers/LocalAuthentication.kt | 2 +- .../simplex/common/views/helpers/ModalView.kt | 48 +++- .../common/views/localauth/LocalAuthView.kt | 2 +- .../common/views/newchat/AddContactView.kt | 2 +- .../common/views/newchat/AddGroupView.kt | 2 +- .../newchat/ContactConnectionInfoView.kt | 4 +- .../common/views/newchat/CreateLinkView.kt | 2 +- .../common/views/newchat/NewChatSheet.kt | 11 +- .../simplex/common/views/newchat/QRCode.kt | 8 +- .../common/views/onboarding/HowItWorks.kt | 2 +- .../common/views/onboarding/SimpleXInfo.kt | 2 +- .../common/views/usersettings/Appearance.kt | 4 +- .../usersettings/NotificationsSettingsView.kt | 2 +- .../views/usersettings/PrivacySettings.kt | 12 +- .../views/usersettings/ProtocolServersView.kt | 4 +- .../usersettings/SetDeliveryReceiptsView.kt | 7 +- .../common/views/usersettings/SettingsView.kt | 46 +++- .../views/usersettings/UserAddressView.kt | 2 +- .../views/usersettings/UserProfilesView.kt | 13 +- .../commonMain/resources/MR/base/strings.xml | 3 + .../kotlin/chat/simplex/common/DesktopApp.kt | 4 +- .../platform/PlatformTextField.desktop.kt | 12 +- .../common/platform/Resources.desktop.kt | 9 +- .../simplex/common/ui/theme/Theme.desktop.kt | 10 + .../views/chat/item/CIImageView.desktop.kt | 2 +- .../views/usersettings/Appearance.desktop.kt | 2 +- 59 files changed, 459 insertions(+), 192 deletions(-) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Theme.android.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt diff --git a/apps/multiplatform/build.gradle.kts b/apps/multiplatform/build.gradle.kts index 4e7ac583a5..94cecd1725 100644 --- a/apps/multiplatform/build.gradle.kts +++ b/apps/multiplatform/build.gradle.kts @@ -55,6 +55,7 @@ allprojects { google() mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + maven("https://jitpack.io") } } diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 5b45a2317f..91c41e562a 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -94,6 +94,7 @@ kotlin { val desktopMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1") + implementation("com.github.Dansoftowner:jSystemThemeDetector:3.6") } } val desktopTest by getting diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt index 2604e0d268..226e97d4dc 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt @@ -48,4 +48,6 @@ actual fun screenOrientation(): ScreenOrientation = when (mainActivity.get()?.re @Composable actual fun screenWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp +actual fun desktopExpandWindowToWidth(width: Dp) {} + actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Theme.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Theme.android.kt new file mode 100644 index 0000000000..0dcbb42322 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/ui/theme/Theme.android.kt @@ -0,0 +1,6 @@ +package chat.simplex.common.ui.theme + +import androidx.compose.runtime.Composable + +@Composable +actual fun isSystemInDarkTheme(): Boolean = androidx.compose.foundation.isSystemInDarkTheme() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt index 25cbffb651..dc8e9dd541 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt @@ -35,7 +35,7 @@ actual fun SimpleAndAnimatedImageView( ImageView(imagePainter) { hideKeyboard(view) if (getLoadedFilePath(file) != null) { - ModalManager.shared.showCustomModal(animated = false) { close -> + ModalManager.fullscreen.showCustomModal(animated = false) { close -> ImageFullScreenView(imageProvider, close) } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index e03e19858a..e02c011fac 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -69,7 +69,7 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod changeIcon = ::setAppIcon, showSettingsModal = showSettingsModal, editColor = { name, initialColor -> - ModalManager.shared.showModalCloseable { close -> + ModalManager.start.showModalCloseable { close -> ColorEditor(name, initialColor, close) } }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index c2ff62442a..dda00fc43c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -1,35 +1,46 @@ package chat.simplex.common import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView import chat.simplex.common.model.* import chat.simplex.common.platform.* -import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.SimpleButton import chat.simplex.common.views.SplashView import chat.simplex.common.views.call.ActiveCallView import chat.simplex.common.views.call.IncomingCallAlertView import chat.simplex.common.views.chat.ChatView -import chat.simplex.common.views.chatlist.ChatListView -import chat.simplex.common.views.chatlist.ShareListView +import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.database.DatabaseErrorView import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.localauth.VerticalDivider import chat.simplex.common.views.onboarding.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged +data class SettingsViewState( + val userPickerState: MutableStateFlow, + val scaffoldState: ScaffoldState, + val switchingUsers: MutableState +) + @Composable fun AppScreen() { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { @@ -66,7 +77,7 @@ fun MainScreen() { } LaunchedEffect(chatModel.clearOverlays.value) { if (chatModel.clearOverlays.value) { - ModalManager.shared.closeModals() + ModalManager.closeAllModalsEverywhere() chatModel.clearOverlays.value = false } } @@ -105,61 +116,30 @@ fun MainScreen() { onboarding == OnboardingStage.OnboardingComplete && userCreated -> { Box { showAdvertiseLAAlert = true - BoxWithConstraints { - var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) } - val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) } - Box( - Modifier - .graphicsLayer { - translationX = -offset.value.dp.toPx() - } - ) { - if (chatModel.setDeliveryReceipts.value) { - SetDeliveryReceiptsView(chatModel) - } else { - val stopped = chatModel.chatRunning.value == false - if (chatModel.sharedContent.value == null) - ChatListView(chatModel, AppLock::setPerformLA, stopped) - else - ShareListView(chatModel, stopped) - } - } - val scope = rememberCoroutineScope() - val onComposed: () -> Unit = { - scope.launch { - offset.animateTo( - if (chatModel.chatId.value == null) 0f else maxWidth.value, - chatListAnimationSpec() - ) - if (offset.value == 0f) { - currentChatId = null - } - } - } - LaunchedEffect(Unit) { - launch { - snapshotFlow { chatModel.chatId.value } - .distinctUntilChanged() - .collect { - if (it != null) currentChatId = it - else onComposed() - } - } - } - Box(Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@{ - currentChatId?.let { - ChatView(it, chatModel, onComposed) - } - } + val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) } + val scaffoldState = rememberScaffoldState() + val switchingUsers = rememberSaveable { mutableStateOf(false) } + val settingsState = remember { SettingsViewState(userPickerState, scaffoldState, switchingUsers) } + if (appPlatform.isAndroid) { + AndroidScreen(settingsState) + } else { + DesktopScreen(settingsState) } } } - onboarding == OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) + onboarding == OnboardingStage.Step1_SimpleXInfo -> { + SimpleXInfo(chatModel, onboarding = true) + if (appPlatform.isDesktop) { + ModalManager.fullscreen.showInView() + } + } onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {} onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel) onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } - ModalManager.shared.showInView() + if (appPlatform.isAndroid) { + ModalManager.fullscreen.showInView() + } val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } } if (unauthorized.value && !(chatModel.activeCallViewIsVisible.value && chatModel.showCallView.value)) { @@ -178,7 +158,7 @@ fun MainScreen() { } else if (chatModel.showCallView.value) { ActiveCallView() } - ModalManager.shared.showPasscodeInView() + ModalManager.fullscreen.showPasscodeInView() val invitation = chatModel.activeCallInvitation.value if (invitation != null) IncomingCallAlertView(invitation, chatModel) AlertManager.shared.showInView() @@ -200,6 +180,141 @@ fun MainScreen() { } } +@Composable +fun AndroidScreen(settingsState: SettingsViewState) { + BoxWithConstraints { + var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) } + val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) } + Box( + Modifier + .graphicsLayer { + translationX = -offset.value.dp.toPx() + } + ) { + StartPartOfScreen(settingsState) + } + val scope = rememberCoroutineScope() + val onComposed: () -> Unit = { + scope.launch { + offset.animateTo( + if (chatModel.chatId.value == null) 0f else maxWidth.value, + chatListAnimationSpec() + ) + if (offset.value == 0f) { + currentChatId = null + } + } + } + LaunchedEffect(Unit) { + launch { + snapshotFlow { chatModel.chatId.value } + .distinctUntilChanged() + .collect { + if (it != null) currentChatId = it + else onComposed() + } + } + } + Box(Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@{ + currentChatId?.let { + ChatView(it, chatModel, onComposed) + } + } + } +} + +@Composable +fun StartPartOfScreen(settingsState: SettingsViewState) { + if (chatModel.setDeliveryReceipts.value) { + SetDeliveryReceiptsView(chatModel) + } else { + val stopped = chatModel.chatRunning.value == false + if (chatModel.sharedContent.value == null) + ChatListView(chatModel, settingsState, AppLock::setPerformLA, stopped) + else + ShareListView(chatModel, settingsState, stopped) + } +} + +@Composable +fun CenterPartOfScreen() { + val currentChatId by remember { ChatModel.chatId } + LaunchedEffect(Unit) { + snapshotFlow { currentChatId } + .distinctUntilChanged() + .collect { + if (it != null) { + ModalManager.center.closeModals() + } + } + } + when (val id = currentChatId) { + null -> { + if (!rememberUpdatedState(ModalManager.center.hasModalsOpen()).value) { + Box( + Modifier + .fillMaxSize() + .background(MaterialTheme.colors.background), + contentAlignment = Alignment.Center + ) { + Text(stringResource(MR.strings.no_selected_chat)) + } + } else { + ModalManager.center.showInView() + } + } + else -> ChatView(id, chatModel) {} + } +} + +@Composable +fun EndPartOfScreen() { + ModalManager.end.showInView() +} + +@Composable +fun DesktopScreen(settingsState: SettingsViewState) { + Box { + // 56.dp is a size of unused space of settings drawer + Box(Modifier.width(DEFAULT_START_MODAL_WIDTH + 56.dp)) { + StartPartOfScreen(settingsState) + } + Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH)) { + ModalManager.start.showInView() + } + Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH).clipToBounds()) { + Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { + CenterPartOfScreen() + } + if (ModalManager.end.hasModalsOpen()) { + VerticalDivider() + } + Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH).clipToBounds()) { + EndPartOfScreen() + } + } + val (userPickerState, scaffoldState, switchingUsers ) = settingsState + val scope = rememberCoroutineScope() + if (scaffoldState.drawerState.isOpen) { + Box( + Modifier + .fillMaxSize() + .padding(start = DEFAULT_START_MODAL_WIDTH) + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { + ModalManager.start.closeModals() + scope.launch { settingsState.scaffoldState.drawerState.close() } + }) + ) + } + VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH)) + UserPicker(chatModel, userPickerState, switchingUsers) { + scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } + } + ModalManager.fullscreen.showInView() + ModalManager.fullscreen.showPasscodeInView() + } +} + @Composable fun InitializationView() { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt index c66eeefd59..fdc1969411 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt @@ -71,7 +71,7 @@ object AppLock { private fun initialEnableLA() { val m = ChatModel val appPrefs = ChatController.appPrefs - appPrefs.laMode.set(LAMode.SYSTEM) + appPrefs.laMode.set(LAMode.default) authenticate( generalGetString(MR.strings.auth_enable_simplex_lock), generalGetString(MR.strings.auth_confirm_credential), @@ -100,7 +100,7 @@ object AppLock { private fun setPasscode() { val appPrefs = ChatController.appPrefs - ModalManager.shared.showCustomModal { close -> + ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( submit = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index ea44745b39..3189e1c705 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -437,7 +437,7 @@ object ChatModel { val info = getChat(id)?.chatInfo as? ChatInfo.ContactConnection ?: return if (info.contactConnection.connReqInv == connReqInv.value) { connReqInv.value = null - ModalManager.shared.closeModals() + ModalManager.center.closeModals() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 065003a072..3bfe32c732 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -72,7 +72,7 @@ class AppPreferences { set = fun(action: CallOnLockScreen) { _callOnLockScreen.set(action.name) } ) val performLA = mkBoolPreference(SHARED_PREFS_PERFORM_LA, false) - val laMode = mkEnumPreference(SHARED_PREFS_LA_MODE, LAMode.SYSTEM) { LAMode.values().firstOrNull { it.name == this } } + val laMode = mkEnumPreference(SHARED_PREFS_LA_MODE, LAMode.default) { LAMode.values().firstOrNull { it.name == this } } val laLockDelay = mkIntPreference(SHARED_PREFS_LA_LOCK_DELAY, 30) val laNoticeShown = mkBoolPreference(SHARED_PREFS_LA_NOTICE_SHOWN, false) val webrtcIceServers = mkStrPreference(SHARED_PREFS_WEBRTC_ICE_SERVERS, null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt index ed2533b488..9760e9c9f2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt @@ -2,7 +2,7 @@ package com.sd.lib.compose.wheel_picker import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme +import chat.simplex.common.ui.theme.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt index 6e410cc2c3..ed35392159 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -10,6 +10,9 @@ enum class AppPlatform { val isAndroid: Boolean get() = this == ANDROID + + val isDesktop: Boolean + get() = this == DESKTOP } expect val appPlatform: AppPlatform diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt index 3d31faaa84..c7799e592b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt @@ -28,4 +28,6 @@ expect fun screenOrientation(): ScreenOrientation @Composable expect fun screenWidth(): Dp +expect fun desktopExpandWindowToWidth(width: Dp) + expect fun isRtl(text: CharSequence): Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index 3f106bd729..6af3156ca2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -1,7 +1,6 @@ package chat.simplex.common.ui.theme import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -191,6 +190,11 @@ val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2 val DEFAULT_BOTTOM_PADDING = 48.dp val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp +val DEFAULT_START_MODAL_WIDTH = 388.dp +val DEFAULT_MIN_CENTER_MODAL_WIDTH = 590.dp +val DEFAULT_END_MODAL_WIDTH = 388.dp +val DEFAULT_MAX_IMAGE_WIDTH = 500.dp + val DarkColorPalette = darkColors( primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files primaryVariant = SimplexBlue, @@ -255,6 +259,15 @@ val CurrentColors: MutableStateFlow = MutableStateFlow @Composable fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight +expect fun isSystemInDarkTheme(): Boolean + +fun reactOnDarkThemeChanges(isDark: Boolean) { + if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == isDark) { + // Change active colors from light to dark and back based on system theme + ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, isDark) + } +} + @Composable fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { LaunchedEffect(darkTheme) { @@ -264,10 +277,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { } val systemDark = isSystemInDarkTheme() LaunchedEffect(systemDark) { - if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == systemDark) { - // Change active colors from light to dark and back based on system theme - ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, systemDark) - } + reactOnDarkThemeChanges(systemDark) } val theme by CurrentColors.collectAsState() MaterialTheme( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index b2692d541a..47bb6918dd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -24,6 +24,12 @@ import chat.simplex.common.platform.* @Composable fun TerminalView(chatModel: ChatModel, close: () -> Unit) { val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) } + val close = { + close() + if (appPlatform.isDesktop) { + ModalManager.center.closeModals() + } + } BackHandler(onBack = { close() }) @@ -126,7 +132,7 @@ fun TerminalLog(terminalItems: List) { modifier = Modifier .fillMaxWidth() .clickable { - ModalManager.shared.showModal(endButtons = { ShareButton { clipboard.shareText(item.details) } }) { + ModalManager.start.showModal(endButtons = { ShareButton { clipboard.shareText(item.details) } }) { SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) { Text(item.details, modifier = Modifier.padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index ba15f426b0..85ab5c2ea3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -81,7 +81,7 @@ fun ChatInfoView( setContactAlias(chat.chatInfo.apiId, it, chatModel) }, openPreferences = { - ModalManager.shared.showCustomModal { close -> + ModalManager.end.showCustomModal { close -> val user = chatModel.currentUser.value if (user != null) { ContactPreferencesView(chatModel, user, contact.contactId, close) @@ -136,7 +136,7 @@ fun ChatInfoView( }) }, verifyClicked = { - ModalManager.shared.showModalCloseable { close -> + ModalManager.end.showModalCloseable { close -> remember { derivedStateOf { (chatModel.getContactChat(contact.contactId)?.chatInfo as? ChatInfo.Direct)?.contact } }.value?.let { ct -> VerifyCodeView( ct.displayName, 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 3478f33ef2..84a9d02bfd 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 @@ -139,7 +139,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { if (chat.chatInfo is ChatInfo.Direct) { val contactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId) val (_, code) = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId) - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> remember { derivedStateOf { (chatModel.getContactChat(chat.chatInfo.apiId)?.chatInfo as? ChatInfo.Direct)?.contact } }.value?.let { ct -> ChatInfoView(chatModel, ct, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close) } @@ -149,7 +150,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { val link = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId) var groupLink = link?.first var groupLinkMemberRole = link?.second - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> GroupChatInfoView(chatModel, groupLink, groupLinkMemberRole, { groupLink = it.first; groupLinkMemberRole = it.second @@ -174,7 +176,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { member to null } setGroupMembers(groupInfo, chatModel) - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> remember { derivedStateOf { chatModel.groupMembers.firstOrNull { it.memberId == member.memberId } } }.value?.let { mem -> GroupMemberInfoView(groupInfo, mem, stats, code, chatModel, close, close) } @@ -314,7 +317,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { withApi { val ciInfo = chatModel.controller.apiGetChatItemInfo(cInfo.chatType, cInfo.apiId, cItem.id) if (ciInfo != null) { - ModalManager.shared.showModal(endButtons = { ShareButton { + ModalManager.end.closeModals() + ModalManager.end.showModal(endButtons = { ShareButton { clipboard.shareText(itemInfoShareText(cItem, ciInfo, chatModel.controller.appPrefs.developerTools.get())) } }) { ChatItemInfoView(cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get()) @@ -326,8 +330,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { hideKeyboard(view) withApi { setGroupMembers(groupInfo, chatModel) - ModalManager.shared.showModalCloseable(true) { close -> - AddGroupMembersView(groupInfo, false, chatModel, close) + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> + AddGroupMembersView(groupInfo, false, chatModel, close) } } }, @@ -462,7 +467,9 @@ fun ChatInfoToolbar( showSearch = false } } - BackHandler(onBack = onBackClicked) + if (appPlatform.isAndroid) { + BackHandler(onBack = onBackClicked) + } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val menuItems = arrayListOf<@Composable () -> Unit>() menuItems.add { @@ -520,7 +527,7 @@ fun ChatInfoToolbar( } DefaultTopAppBar( - navigationButton = { NavigationButtonBack(onBackClicked) }, + navigationButton = { if (appPlatform.isAndroid || showSearch) { NavigationButtonBack(onBackClicked) } }, title = { ChatInfoToolbarTitle(chat.chatInfo) }, onTitleClick = info, showSearch = showSearch, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 6912ced0e8..143815ddc2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -170,7 +170,7 @@ fun ComposeView( val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() val maxFileSize = getMaxFileSize(FileProtocol.XFTP) val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) - val textStyle = remember { mutableStateOf(smallFont) } + val textStyle = remember(MaterialTheme.colors.isLight) { mutableStateOf(smallFont) } val processPickedMedia = { uris: List, text: String? -> val content = ArrayList() val imagesPreview = ArrayList() @@ -655,7 +655,11 @@ fun ComposeView( } else { showChooseAttachment } - IconButton(attachmentClicked, enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value) { + IconButton( + attachmentClicked, + Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else 7.dp), + enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value + ) { Icon( painterResource(MR.images.ic_attach_file_filled_500), contentDescription = stringResource(MR.strings.attach), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index df76617a23..af8f4520b2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -83,7 +83,7 @@ fun SendMsgView( if (showDeleteTextButton.value) { DeleteTextButton(composeState) } - Box(Modifier.align(Alignment.BottomEnd)) { + Box(Modifier.align(Alignment.BottomEnd).padding(bottom = if (appPlatform.isAndroid) 0.dp else 5.dp)) { val sendButtonSize = remember { Animatable(36f) } val sendButtonAlpha = remember { Animatable(1f) } val scope = rememberCoroutineScope() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index bb79fce66c..1b25c52cfe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -113,7 +113,7 @@ private fun VerifyCodeLayout( } else { if (appPlatform.isAndroid) { SimpleButton(generalGetString(MR.strings.scan_code), painterResource(MR.images.ic_qr_code)) { - ModalManager.shared.showModal { + ModalManager.end.showModal { ScanCodeView(verifyCode) { } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index 05dbab28a7..e9a9854456 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -50,7 +50,7 @@ fun AddGroupMembersView(groupInfo: GroupInfo, creatingGroup: Boolean = false, ch allowModifyMembers = allowModifyMembers, searchText, openPreferences = { - ModalManager.shared.showCustomModal { close -> + ModalManager.end.showCustomModal { close -> GroupPreferencesView(chatModel, groupInfo.id, close) } }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 77cfe964c0..4b37badfeb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -54,7 +54,7 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR addMembers = { withApi { setGroupMembers(groupInfo, chatModel) - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.end.showModalCloseable(true) { close -> AddGroupMembersView(groupInfo, false, chatModel, close) } } @@ -73,7 +73,7 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR } else { member to null } - ModalManager.shared.showModalCloseable(true) { closeCurrent -> + ModalManager.end.showModalCloseable(true) { closeCurrent -> remember { derivedStateOf { chatModel.groupMembers.firstOrNull { it.memberId == member.memberId } } }.value?.let { mem -> GroupMemberInfoView(groupInfo, mem, stats, code, chatModel, closeCurrent) { closeCurrent() @@ -84,13 +84,13 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR } }, editGroupProfile = { - ModalManager.shared.showCustomModal { close -> GroupProfileView(groupInfo, chatModel, close) } + ModalManager.end.showCustomModal { close -> GroupProfileView(groupInfo, chatModel, close) } }, addOrEditWelcomeMessage = { - ModalManager.shared.showCustomModal { close -> GroupWelcomeView(chatModel, groupInfo, close) } + ModalManager.end.showCustomModal { close -> GroupWelcomeView(chatModel, groupInfo, close) } }, openPreferences = { - ModalManager.shared.showCustomModal { close -> + ModalManager.end.showCustomModal { close -> GroupPreferencesView( chatModel, chat.id, @@ -102,7 +102,7 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) }, leaveGroup = { leaveGroupDialog(groupInfo, chatModel, close) }, manageGroupLink = { - ModalManager.shared.showModal { GroupLinkView(chatModel, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) } + ModalManager.end.showModal { GroupLinkView(chatModel, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) } } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index b712a629c1..bfe9aff82b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -149,7 +149,7 @@ fun GroupMemberInfoView( }) }, verifyClicked = { - ModalManager.shared.showModalCloseable { close -> + ModalManager.end.showModalCloseable { close -> remember { derivedStateOf { chatModel.groupMembers.firstOrNull { it.memberId == member.memberId } } }.value?.let { mem -> VerifyCodeView( mem.displayName, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index db772d9dc6..6430a23db8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.DEFAULT_MAX_IMAGE_WIDTH import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import java.io.File @@ -85,7 +86,7 @@ fun CIImageView( @Composable fun imageViewFullWidth(): Dp { val approximatePadding = 100.dp - return with(LocalDensity.current) { minOf(1000.dp, LocalWindowWidth() - approximatePadding) } + return with(LocalDensity.current) { minOf(DEFAULT_MAX_IMAGE_WIDTH, LocalWindowWidth() - approximatePadding) } } @Composable @@ -93,10 +94,10 @@ fun CIImageView( Image( imageBitmap, contentDescription = stringResource(MR.strings.image_descr), - // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView + // .width(DEFAULT_MAX_IMAGE_WIDTH) is a hack for image to increase IntrinsicSize of FramedItemView // if text is short and take all available width if text is long modifier = Modifier - .width(if (imageBitmap.width * 0.97 <= imageBitmap.height) imageViewFullWidth() * 0.75f else 1000.dp) + .width(if (imageBitmap.width * 0.97 <= imageBitmap.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH) .combinedClickable( onLongClick = { showMenu.value = true }, onClick = onClick @@ -110,10 +111,10 @@ fun CIImageView( Image( painter, contentDescription = stringResource(MR.strings.image_descr), - // .width(1000.dp) is a hack for image to increase IntrinsicSize of FramedItemView + // .width(DEFAULT_MAX_IMAGE_WIDTH) is a hack for image to increase IntrinsicSize of FramedItemView // if text is short and take all available width if text is long modifier = Modifier - .width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else 1000.dp) + .width(if (painter.intrinsicSize.width * 0.97 <= painter.intrinsicSize.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH) .combinedClickable( onLongClick = { showMenu.value = true }, onClick = onClick diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt index 92baa6a971..21c27fa34e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt @@ -21,7 +21,11 @@ import chat.simplex.res.MR @Composable fun CIInvalidJSONView(json: String) { Row(Modifier - .clickable { ModalManager.shared.showModal(true) { InvalidJSONView(json) } } + .clickable { + ModalManager.center.closeModals() + ModalManager.end.closeModals() + ModalManager.center.showModal(true) { InvalidJSONView(json) } + } .padding(horizontal = 10.dp, vertical = 6.dp) ) { Text(stringResource(MR.strings.invalid_data), color = Color.Red, fontStyle = FontStyle.Italic) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt index 650c525695..22d89e767c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt @@ -44,7 +44,7 @@ fun CIVideoView( val view = LocalMultiplatformView() VideoView(uri, file, preview, duration * 1000L, showMenu, onClick = { hideKeyboard(view) - ModalManager.shared.showCustomModal(animated = false) { close -> + ModalManager.fullscreen.showCustomModal(animated = false) { close -> ImageFullScreenView(imageProvider, close) } }) @@ -113,7 +113,7 @@ private fun VideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defau } Box { val windowWidth = LocalWindowWidth() - val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp } + val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else DEFAULT_MAX_IMAGE_WIDTH } PlayerView( player, width, @@ -202,7 +202,7 @@ private fun DurationProgress(file: CIFile, playing: MutableState, durat @Composable private fun ImageView(preview: ImageBitmap, showMenu: MutableState, onClick: () -> Unit) { val windowWidth = LocalWindowWidth() - val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else 1000.dp } + val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else DEFAULT_MAX_IMAGE_WIDTH } Image( preview, contentDescription = stringResource(MR.strings.video_descr), @@ -311,5 +311,5 @@ private fun receiveFileIfValidSize(file: CIFile, receiveFile: (Long) -> Unit) { private fun videoViewFullWidth(windowWidth: Dp): Dp { val approximatePadding = 100.dp - return minOf(1000.dp, windowWidth - approximatePadding) + return minOf(DEFAULT_MAX_IMAGE_WIDTH, windowWidth - approximatePadding) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index de2d67b48c..73cb007898 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -25,6 +25,7 @@ import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.ContactConnectionInfoView import chat.simplex.common.model.* +import chat.simplex.common.platform.appPlatform import chat.simplex.common.platform.ntfManager import chat.simplex.res.MR import kotlinx.coroutines.delay @@ -73,7 +74,9 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { ChatListNavLinkLayout( chatLinkPreview = { ContactConnectionView(chat.chatInfo.contactConnection) }, click = { - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.center.closeModals() + ModalManager.end.closeModals() + ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close -> ContactConnectionInfoView(chatModel, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close) } }, @@ -87,7 +90,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { InvalidDataView() }, click = { - ModalManager.shared.showModal(true) { InvalidJSONView(chat.chatInfo.json) } + ModalManager.end.closeModals() + ModalManager.center.showModal(true) { InvalidJSONView(chat.chatInfo.json) } }, dropdownMenuItems = null, showMenu, @@ -342,7 +346,9 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: stringResource(MR.strings.set_contact_name), painterResource(MR.images.ic_edit), onClick = { - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.center.closeModals() + ModalManager.end.closeModals() + ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close -> ContactConnectionInfoView(chatModel, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close) } showMenu.value = false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 9c372020fc..ec5d0071c0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -14,10 +14,9 @@ import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.LocalUriHandler import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.unit.* +import chat.simplex.common.SettingsViewState import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -34,9 +33,8 @@ import kotlinx.coroutines.launch import java.net.URI @Composable -fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { +fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { val newChatSheetState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) } - val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) } val showNewChatSheet = { newChatSheetState.value = AnimatedViewState.VISIBLE } @@ -47,7 +45,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: LaunchedEffect(Unit) { if (shouldShowWhatsNew(chatModel)) { delay(1000L) - ModalManager.shared.showCustomModal { close -> WhatsNewView(close = close) } + ModalManager.center.showCustomModal { close -> WhatsNewView(close = close) } } } LaunchedEffect(chatModel.clearOverlays.value) { @@ -60,13 +58,13 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: connectIfOpenedViaUri(url, chatModel) } } + val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp var searchInList by rememberSaveable { mutableStateOf("") } - val scaffoldState = rememberScaffoldState() val scope = rememberCoroutineScope() - val switchingUsers = rememberSaveable { mutableStateOf(false) } - Scaffold(topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } }, + val (userPickerState, scaffoldState, switchingUsers ) = settingsState + Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } } }, scaffoldState = scaffoldState, - drawerContent = { SettingsView(chatModel, setPerformLA) }, + drawerContent = { SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) }, drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f), floatingActionButton = { if (searchInList.isEmpty()) { @@ -76,7 +74,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: if (newChatSheetState.value.isVisible()) hideNewChatSheet(true) else showNewChatSheet() } }, - Modifier.padding(end = DEFAULT_PADDING - 16.dp, bottom = DEFAULT_PADDING - 16.dp), + Modifier.padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp), elevation = FloatingActionButtonDefaults.elevation( defaultElevation = 0.dp, pressedElevation = 0.dp, @@ -91,7 +89,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: } } ) { - Box(Modifier.padding(it)) { + Box(Modifier.padding(it).padding(end = endPadding)) { Column( modifier = Modifier .fillMaxSize() @@ -112,8 +110,10 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: if (searchInList.isEmpty()) { NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet) } - UserPicker(chatModel, userPickerState, switchingUsers) { - scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } + if (appPlatform.isAndroid) { + UserPicker(chatModel, userPickerState, switchingUsers) { + scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } + } } if (switchingUsers.value) { Box( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index b3daa610e8..2d0ed7eb53 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -13,20 +13,24 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import chat.simplex.common.SettingsViewState import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.model.Chat import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.BackHandler +import chat.simplex.common.platform.appPlatform import chat.simplex.res.MR import kotlinx.coroutines.flow.MutableStateFlow @Composable -fun ShareListView(chatModel: ChatModel, stopped: Boolean) { +fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stopped: Boolean) { var searchInList by rememberSaveable { mutableStateOf("") } - val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) } - val switchingUsers = rememberSaveable { mutableStateOf(false) } + val (userPickerState, scaffoldState, switchingUsers) = settingsState + val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp Scaffold( + Modifier.padding(end = endPadding), + scaffoldState = scaffoldState, topBar = { Column { ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } } }, ) { Box(Modifier.padding(it)) { @@ -42,9 +46,11 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) { } } } - UserPicker(chatModel, userPickerState, switchingUsers, showSettings = false, showCancel = true, cancelClicked = { - chatModel.sharedContent.value = null - }) + if (appPlatform.isAndroid) { + UserPicker(chatModel, userPickerState, switchingUsers, showSettings = false, showCancel = true, cancelClicked = { + chatModel.sharedContent.value = null + }) + } } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index e8fbb66635..5381ebc283 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -41,6 +41,11 @@ fun UserPicker( ) { val scope = rememberCoroutineScope() var newChat by remember { mutableStateOf(userPickerState.value) } + if (newChat.isVisible()) { + BackHandler { + userPickerState.value = AnimatedViewState.HIDING + } + } val users by remember { derivedStateOf { chatModel.users @@ -121,6 +126,7 @@ fun UserPicker( delay(500) switchingUsers.value = true } + ModalManager.closeAllModalsEverywhere() chatModel.controller.changeActiveUser(u.user.userId, null) job.cancel() switchingUsers.value = false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index 3963c69ae3..8738db2df1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -83,7 +83,7 @@ private fun deleteArchiveAlert(m: ChatModel, archivePath: String) { if (fileDeleted) { m.controller.appPrefs.chatArchiveName.set(null) m.controller.appPrefs.chatArchiveTime.set(null) - ModalManager.shared.closeModal() + ModalManager.start.closeModal() } else { Log.e(TAG, "deleteArchiveAlert delete() error") } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 32d2379f75..8c26011e92 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -357,11 +357,11 @@ private fun startChat(m: ChatModel, runChat: MutableState, chatLastSta } if (m.chatDbStatus.value !is DBMigrationResult.OK) { /** Hide current view and show [DatabaseErrorView] */ - ModalManager.shared.closeModals() + ModalManager.closeAllModalsEverywhere() return@withApi } if (m.currentUser.value == null) { - ModalManager.shared.closeModals() + ModalManager.closeAllModalsEverywhere() return@withApi } else { m.controller.apiStartChat() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index 0877cb42e9..848b21eb44 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.ui.theme.* @Composable -fun CloseSheetBar(close: (() -> Unit)?, endButtons: @Composable RowScope.() -> Unit = {}) { +fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}) { Column( Modifier .fillMaxWidth() @@ -32,7 +32,11 @@ fun CloseSheetBar(close: (() -> Unit)?, endButtons: @Composable RowScope.() -> U horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - NavigationButtonBack(onButtonClicked = close) + if (showClose) { + NavigationButtonBack(onButtonClicked = close) + } else { + Spacer(Modifier) + } Row { endButtons() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt index b6b512e03a..8e6c9fffc1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt @@ -45,7 +45,7 @@ fun authenticateWithPasscode( completed: (LAResult) -> Unit ) { val password = DatabaseUtils.ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password))) - ModalManager.shared.showPasscodeCustomModal { close -> + ModalManager.fullscreen.showPasscodeCustomModal { close -> BackHandler { close() completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled))) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 598ceaab8c..ec3ee8ecea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -8,43 +8,51 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* -import chat.simplex.common.ui.theme.themedBackground +import chat.simplex.common.ui.theme.* import java.util.concurrent.atomic.AtomicBoolean @Composable fun ModalView( close: () -> Unit, + showClose: Boolean = true, background: Color = MaterialTheme.colors.background, modifier: Modifier = Modifier, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit, ) { - BackHandler(onBack = close) + if (showClose) { + BackHandler(onBack = close) + } Surface(Modifier.fillMaxSize()) { Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) { - CloseSheetBar(close, endButtons) + CloseSheetBar(close, showClose, endButtons) Box(modifier) { content() } } } } -class ModalManager { +enum class ModalPlacement { + START, CENTER, END, FULLSCREEN +} + +class ModalManager(private val placement: ModalPlacement? = null) { private val modalViews = arrayListOf Unit) -> Unit)>>() private val modalCount = mutableStateOf(0) private val toRemove = mutableSetOf() private var oldViewChanging = AtomicBoolean(false) private var passcodeView: MutableState<(@Composable (close: () -> Unit) -> Unit)?> = mutableStateOf(null) - fun showModal(settings: Boolean = false, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit) { + fun showModal(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit) { showCustomModal { close -> - ModalView(close, endButtons = endButtons, content = content) + ModalView(close, showClose = showClose, endButtons = endButtons, content = content) } } - fun showModalCloseable(settings: Boolean = false, content: @Composable (close: () -> Unit) -> Unit) { + fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, content: @Composable (close: () -> Unit) -> Unit) { showCustomModal { close -> - ModalView(close, content = { content(close) }) + ModalView(close, showClose = showClose, content = { content(close) }) } } @@ -55,8 +63,17 @@ class ModalManager { if (toRemove.isNotEmpty()) { runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } } } - modalViews.add(animated to modal) + // Make animated appearance only on Android (everytime) and on Desktop (when it's on the start part of the screen or modals > 0) + // to prevent unneeded animation on different situations + val anim = if (appPlatform.isAndroid) animated else animated && (modalCount.value > 0 || placement == ModalPlacement.START) + modalViews.add(anim to modal) modalCount.value = modalViews.size - toRemove.size + + if (placement == ModalPlacement.CENTER) { + ChatModel.chatId.value = null + } else if (placement == ModalPlacement.END) { + desktopExpandWindowToWidth(DEFAULT_START_MODAL_WIDTH + DEFAULT_MIN_CENTER_MODAL_WIDTH + DEFAULT_END_MODAL_WIDTH) + } } fun showPasscodeCustomModal(modal: @Composable (close: () -> Unit) -> Unit) { @@ -146,6 +163,17 @@ private fun animationSpec() = tween(durationMillis = 250, easing = FastOu // private fun animationSpecFromEnd() = tween(durationMillis = 100, easing = FastOutSlowInEasing) companion object { - val shared = ModalManager() + private val shared = ModalManager() + val start = if (appPlatform.isAndroid) shared else ModalManager(ModalPlacement.START) + val center = if (appPlatform.isAndroid) shared else ModalManager(ModalPlacement.CENTER) + val end = if (appPlatform.isAndroid) shared else ModalManager(ModalPlacement.END) + val fullscreen = if (appPlatform.isAndroid) shared else ModalManager(ModalPlacement.FULLSCREEN) + + fun closeAllModalsEverywhere() { + start.closeModals() + center.closeModals() + end.closeModals() + fullscreen.closeModals() + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 0719c5da37..756e605dc3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -70,7 +70,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( if (createdUser != null) { m.controller.startChat(createdUser) } - ModalManager.shared.closeModals() + ModalManager.fullscreen.closeModals() AlertManager.shared.hideAlert() completed(LAResult.Success) } catch (e: Exception) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt index e7de6154ea..2ee2145259 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt @@ -30,7 +30,7 @@ fun AddContactView(connReqInvitation: String, connIncognito: Boolean) { connIncognito = connIncognito, share = { clipboard.shareText(connReqInvitation) }, learnMore = { - ModalManager.shared.showModal { + ModalManager.center.showModal { Column( Modifier .fillMaxHeight() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index c89e0e43fb..3ab5ef9b2b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -45,7 +45,7 @@ fun AddGroupView(chatModel: ChatModel, close: () -> Unit) { chatModel.chatId.value = groupInfo.id setGroupMembers(groupInfo, chatModel) close.invoke() - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.end.showModalCloseable(true) { close -> AddGroupMembersView(groupInfo, true, chatModel, close) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 07d937823b..2b971282b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -42,7 +42,7 @@ fun ContactConnectionInfoView( **/ DisposableEffect(Unit) { onDispose { - if (!ModalManager.shared.hasModalsOpen()) { + if (!ModalManager.center.hasModalsOpen()) { chatModel.connReqInv.value = null } } @@ -57,7 +57,7 @@ fun ContactConnectionInfoView( onLocalAliasChanged = { setContactAlias(contactConnection, it, chatModel) }, share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) }, learnMore = { - ModalManager.shared.showModal { + ModalManager.center.showModal { Column( Modifier .fillMaxHeight() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt index 3d6cfb2f91..53f7bdcc73 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt @@ -35,7 +35,7 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { **/ DisposableEffect(Unit) { onDispose { - if (!ModalManager.shared.hasModalsOpen()) { + if (!ModalManager.center.hasModalsOpen()) { m.connReqInv.value = null } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index a51d070f85..171465d86d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -40,15 +40,18 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow ConnectViaLinkView(chatModel, close) } + ModalManager.center.closeModals() + ModalManager.center.showModalCloseable { close -> ConnectViaLinkView(chatModel, close) } }, createGroup = { closeNewChatSheet(false) - ModalManager.shared.showCustomModal { close -> AddGroupView(chatModel, close) } + ModalManager.center.closeModals() + ModalManager.center.showCustomModal { close -> AddGroupView(chatModel, close) } }, closeNewChatSheet, ) @@ -93,10 +96,12 @@ private fun NewChatSheetLayout( } } } + val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp val maxWidth = with(LocalDensity.current) { screenWidth() * density } Column( Modifier .fillMaxSize() + .padding(end = endPadding) .offset { IntOffset(if (newChat.isGone()) -maxWidth.value.roundToInt() else 0, 0) } .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) { closeNewChatSheet(true) } .drawBehind { drawRect(animatedColor.value) }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index 9882caa55d..5e43a34197 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -4,9 +4,11 @@ import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.* +import androidx.compose.ui.unit.dp import dev.icerock.moko.resources.compose.stringResource import boofcv.alg.drawing.FiducialImageEngine import boofcv.alg.fiducial.qrcode.* @@ -25,7 +27,7 @@ fun QRCode( ) { val scope = rememberCoroutineScope() - BoxWithConstraints { + BoxWithConstraints(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { val maxWidthInPx = with(LocalDensity.current) { maxWidth.roundToPx() } val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) { qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) @@ -34,7 +36,9 @@ fun QRCode( Image( bitmap = qr, contentDescription = stringResource(MR.strings.image_descr_qr_code), - modifier + Modifier + .widthIn(max = 500.dp) + .then(modifier) .clickable { scope.launch { val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index a506c8daff..7785180926 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -42,7 +42,7 @@ fun HowItWorks(user: User?, onboardingStage: MutableState? = n if (onboardingStage != null) { Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) { - OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.shared.closeModal() }) + OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.fullscreen.closeModal() }) } Spacer(Modifier.fillMaxHeight().weight(1f)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index ddb1e81ec4..1e69d92c22 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -26,7 +26,7 @@ fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { SimpleXInfoLayout( user = chatModel.currentUser.value, onboardingStage = if (onboarding) chatModel.onboardingStage else null, - showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } }, + showModal = { modalView -> { if (onboarding) ModalManager.fullscreen.showModal { modalView(chatModel) } else ModalManager.start.showModal { modalView(chatModel) } } }, ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index e1e35ace8d..f9c85ca56d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -41,7 +41,7 @@ object AppearanceScope { ) { val currentTheme by CurrentColors.collectAsState() SectionView(stringResource(MR.strings.settings_section_title_themes)) { - val darkTheme = isSystemInDarkTheme() + val darkTheme = chat.simplex.common.ui.theme.isSystemInDarkTheme() val state = remember { derivedStateOf { currentTheme.name } } ThemeSelector(state) { ThemeManager.applyTheme(it, darkTheme) @@ -226,7 +226,7 @@ object AppearanceScope { @Composable private fun ThemeSelector(state: State, onSelected: (String) -> Unit) { - val darkTheme = isSystemInDarkTheme() + val darkTheme = chat.simplex.common.ui.theme.isSystemInDarkTheme() val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third }) } ExposedDropDownSettingRow( generalGetString(MR.strings.theme), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt index 6c59bb1508..a3da6c6c90 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt @@ -32,7 +32,7 @@ fun NotificationsSettingsView( notificationsMode = remember { chatModel.controller.appPrefs.notificationsMode.state }, notificationPreviewMode = chatModel.notificationPreviewMode, showPage = { page -> - ModalManager.shared.showModalCloseable(true) { + ModalManager.start.showModalCloseable(true) { when (page) { CurrentPage.NOTIFICATIONS_MODE -> NotificationsModeView(chatModel.controller.appPrefs.notificationsMode.state) { changeNotificationsMode(it, chatModel) } CurrentPage.NOTIFICATION_PREVIEW_MODE -> NotificationPreviewView(chatModel.notificationPreviewMode, onNotificationPreviewModeSelected) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 0b00df1c43..9c7c4e25c5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -278,7 +278,7 @@ fun SimplexLockView( } } LAMode.PASSCODE -> { - ModalManager.shared.showCustomModal { close -> + ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( submit = { @@ -306,7 +306,7 @@ fun SimplexLockView( is LAResult.Failed -> { /* Can be called multiple times on every failure */ } LAResult.Success -> { if (!selfDestruct.get()) { - ModalManager.shared.showCustomModal { close -> + ModalManager.fullscreen.showCustomModal { close -> EnableSelfDestruct(selfDestruct, close) } } else { @@ -322,7 +322,7 @@ fun SimplexLockView( authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.la_change_app_passcode)) { laResult -> when (laResult) { LAResult.Success -> { - ModalManager.shared.showCustomModal { close -> + ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( submit = { @@ -345,7 +345,7 @@ fun SimplexLockView( authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_passcode)) { laResult -> when (laResult) { LAResult.Success -> { - ModalManager.shared.showCustomModal { close -> + ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( passcodeKeychain = ksSelfDestructPassword, @@ -380,7 +380,7 @@ fun SimplexLockView( setPerformLA(true) } LAMode.PASSCODE -> { - ModalManager.shared.showCustomModal { close -> + ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { SetAppPasscodeView( submit = { @@ -427,7 +427,7 @@ fun SimplexLockView( SectionDividerSpaced() SectionView(stringResource(MR.strings.self_destruct_passcode).uppercase()) { val openInfo = { - ModalManager.shared.showModal { + ModalManager.start.showModal { SelfDestructInfoView() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index a6ee09f749..92246b72fb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -62,7 +62,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () } fun showServer(server: ServerCfg) { - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.start.showModalCloseable(true) { close -> var old by remember { mutableStateOf(server) } val index = servers.indexOf(old) ProtocolServerView( @@ -117,7 +117,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () if (appPlatform.isAndroid) { SectionItemView({ AlertManager.shared.hideAlert() - ModalManager.shared.showModalCloseable { close -> + ModalManager.start.showModalCloseable { close -> ScanProtocolServer { close() servers = servers + it diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index 1189c8bba6..089ec7713f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -11,10 +11,10 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* import chat.simplex.res.MR -import chat.simplex.common.platform.TAG -import chat.simplex.common.platform.Log import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* @@ -73,8 +73,9 @@ private fun SetDeliveryReceiptsLayout( skip: () -> Unit, userCount: Int, ) { + val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp Column( - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING), + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING, end = endPadding), horizontalAlignment = Alignment.CenterHorizontally, ) { AppBarTitle(stringResource(MR.strings.delivery_receipts_title)) 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 3226dc85be..fda24031aa 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 @@ -23,16 +23,17 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* -import chat.simplex.common.platform.appVersionInfo +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.database.DatabaseView import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.SimpleXInfo import chat.simplex.common.views.onboarding.WhatsNewView import chat.simplex.res.MR +import kotlinx.coroutines.launch @Composable -fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { +fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerState: DrawerState) { val user = chatModel.currentUser.value val stopped = chatModel.chatRunning.value == false @@ -48,10 +49,10 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { chatModel.controller.appPrefs.incognito, user.displayName, setPerformLA = setPerformLA, - showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } }, - showSettingsModal = { modalView -> { ModalManager.shared.showModal(true) { modalView(chatModel) } } }, + showModal = { modalView -> { ModalManager.start.showModal { modalView(chatModel) } } }, + showSettingsModal = { modalView -> { ModalManager.start.showModal(true) { modalView(chatModel) } } }, showSettingsModalWithSearch = { modalView -> - ModalManager.shared.showCustomModal { close -> + ModalManager.start.showCustomModal { close -> val search = rememberSaveable { mutableStateOf("") } ModalView( { close() }, @@ -61,12 +62,12 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { content = { modalView(chatModel, search) }) } }, - showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } }, + showCustomModal = { modalView -> { ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } } }, showVersion = { withApi { val info = chatModel.controller.apiGetVersion() if (info != null) { - ModalManager.shared.showModal { VersionInfoView(info) } + ModalManager.start.showModal { VersionInfoView(info) } } } }, @@ -75,7 +76,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { block() } else { var autoShow = true - ModalManager.shared.showModalCloseable { close -> + ModalManager.fullscreen.showModalCloseable { close -> val onFinishAuth = { success: Boolean -> if (success) { close() @@ -104,6 +105,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { } } }, + drawerState = drawerState, ) } } @@ -125,15 +127,25 @@ fun SettingsLayout( showSettingsModalWithSearch: (@Composable (ChatModel, MutableState) -> Unit) -> Unit, showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, - withAuth: (title: String, desc: String, block: () -> Unit) -> Unit + withAuth: (title: String, desc: String, block: () -> Unit) -> Unit, + drawerState: DrawerState, ) { + val scope = rememberCoroutineScope() + val closeSettings: () -> Unit = { scope.launch { drawerState.close() } } + if (drawerState.isOpen) { + BackHandler { + closeSettings() + } + } val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current - Box(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).themedBackground(theme.value.base)) { + Box(Modifier.fillMaxSize()) { Column( Modifier .fillMaxSize() - .padding(top = DEFAULT_PADDING) + .verticalScroll(rememberScrollState()) + .themedBackground(theme.value.base) + .padding(top = if (appPlatform.isAndroid) DEFAULT_PADDING else DEFAULT_PADDING * 3) ) { AppBarTitle(stringResource(MR.strings.your_settings)) @@ -178,6 +190,17 @@ fun SettingsLayout( SettingsSectionApp(showSettingsModal, showCustomModal, showVersion, withAuth) SectionBottomSpacer() } + if (appPlatform.isDesktop) { + Box( + Modifier + .fillMaxWidth() + .background(MaterialTheme.colors.background) + .background(if (isInDarkTheme()) ToolbarDark else ToolbarLight) + .padding(start = 4.dp, top = 8.dp) + ) { + NavigationButtonBack(closeSettings) + } + } } } @@ -510,6 +533,7 @@ fun PreviewSettingsLayout() { showCustomModal = { {} }, showVersion = {}, withAuth = { _, _, _ -> }, + drawerState = DrawerState(DrawerValue.Closed), ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index d907e0fff5..63f06a2aec 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -85,7 +85,7 @@ fun UserAddressView( } }, learnMore = { - ModalManager.shared.showModal { + ModalManager.start.showModal { Column( Modifier .fillMaxHeight() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index b788787854..ac3a68fc49 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -30,6 +30,7 @@ import chat.simplex.common.views.database.PassphraseField import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.CreateProfile import chat.simplex.common.model.* +import chat.simplex.common.platform.appPlatform import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.delay @@ -47,11 +48,15 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: showHiddenProfilesNotice = m.controller.appPrefs.showHiddenProfilesNotice, visibleUsersCount = visibleUsersCount(m), addUser = { - ModalManager.shared.showModalCloseable { close -> + ModalManager.center.showModalCloseable { close -> CreateProfile(m, close) } }, activateUser = { user -> + if (appPlatform.isDesktop) { + ModalManager.center.closeModals() + ModalManager.end.closeModals() + } withBGApi { m.controller.changeActiveUser(user.userId, userViewPassword(user, searchTextOrPassword.value.trim())) } @@ -99,7 +104,7 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: }, unhideUser = { user -> if (passwordEntryRequired(user, searchTextOrPassword.value)) { - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.start.showModalCloseable(true) { close -> ProfileActionView(UserProfileAction.UNHIDE, user) { pwd -> withBGApi { setUserPrivacy(m) { m.controller.apiUnhideUser(user.userId, pwd) } @@ -122,7 +127,7 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: withBGApi { setUserPrivacy(m) { m.controller.apiUnmuteUser(user.userId) } } }, showHiddenProfile = { user -> - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.start.showModalCloseable(true) { close -> HiddenProfileView(m, user) { profileHidden.value = true withBGApi { @@ -328,7 +333,7 @@ private fun passwordEntryRequired(user: User, searchTextOrPassword: String): Boo private fun removeUser(m: ChatModel, user: User, users: List, delSMPQueues: Boolean, searchTextOrPassword: String) { if (passwordEntryRequired(user, searchTextOrPassword)) { - ModalManager.shared.showModalCloseable(true) { close -> + ModalManager.start.showModalCloseable(true) { close -> ProfileActionView(UserProfileAction.DELETE, user) { pwd -> withBGApi { doRemoveUser(m, user, users, delSMPQueues, pwd) 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 32e8c4ce88..ac7451c389 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -267,6 +267,9 @@ You have no chats No filtered chats + + No selected chat + Share message… Share media… diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 4c5cfb2526..936942d980 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -24,7 +24,8 @@ import java.io.File val simplexWindowState = SimplexWindowState() fun showApp() = application { - val windowState = rememberWindowState(placement = WindowPlacement.Floating) + val windowState = rememberWindowState(placement = WindowPlacement.Floating, width = 1366.dp, height = 768.dp) + simplexWindowState.windowState = windowState Window(state = windowState, onCloseRequest = ::exitApplication, onKeyEvent = { if (it.key == Key.Escape && it.type == KeyEventType.KeyUp) { simplexWindowState.backstack.lastOrNull()?.invoke() != null @@ -108,6 +109,7 @@ fun showApp() = application { } class SimplexWindowState { + lateinit var windowState: WindowState val backstack = mutableStateListOf<() -> Unit>() val openDialog = DialogState() val openMultipleDialog = DialogState>() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt index 7148f8217e..ea806c0622 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt @@ -38,7 +38,7 @@ actual fun PlatformTextField( val cs = composeState.value val focusRequester = remember { FocusRequester() } val keyboard = LocalSoftwareKeyboardController.current - val padding = PaddingValues(12.dp, 7.dp, 45.dp, 0.dp) + val padding = PaddingValues(12.dp, 12.dp, 45.dp, 0.dp) LaunchedEffect(cs.contextItem) { if (cs.contextItem !is ComposeContextItem.QuotedItem) return@LaunchedEffect // In replying state @@ -74,14 +74,10 @@ actual fun PlatformTextField( CompositionLocalProvider( LocalLayoutDirection provides if (isRtl) LayoutDirection.Rtl else LocalLayoutDirection.current ) { - Box( - Modifier - .weight(1f) - .padding(horizontal = 12.dp) - .padding(top = 4.dp) - .padding(bottom = 6.dp) - ) { + Column(Modifier.weight(1f).padding(start = 12.dp, end = 32.dp)) { + Spacer(Modifier.height(8.dp)) innerTextField() + Spacer(Modifier.height(10.dp)) } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt index 1bedcb6f44..3a76808b0c 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt @@ -4,8 +4,8 @@ import androidx.compose.runtime.* import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.* +import chat.simplex.common.simplexWindowState import com.russhwolf.settings.* import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.desc.desc @@ -49,6 +49,11 @@ actual fun screenWidth(): Dp { return width*/ }// LALAL java.awt.Desktop.getDesktop() +actual fun desktopExpandWindowToWidth(width: Dp) { + if (simplexWindowState.windowState.size.width >= width) return + simplexWindowState.windowState.size = simplexWindowState.windowState.size.copy(width = width) +} + actual fun isRtl(text: CharSequence): Boolean { if (text.isEmpty()) return false return text.any { char -> diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt new file mode 100644 index 0000000000..d7da2fa123 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt @@ -0,0 +1,10 @@ +package chat.simplex.common.ui.theme + +import com.jthemedetecor.OsThemeDetector + +private val detector: OsThemeDetector = OsThemeDetector.getDetector() + .apply { + registerListener(::reactOnDarkThemeChanges) + } + +actual fun isSystemInDarkTheme(): Boolean = detector.isDark diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt index a9c7f5f0ae..214946b1c9 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt @@ -20,7 +20,7 @@ actual fun SimpleAndAnimatedImageView( // LALAL make it animated too ImageView(imageBitmap.toAwtImage().toPainter()) { if (getLoadedFilePath(file) != null) { - ModalManager.shared.showCustomModal(animated = false) { close -> + ModalManager.fullscreen.showCustomModal(animated = false) { close -> ImageFullScreenView(imageProvider, close) } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 851cf137dd..1a8ad010e3 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -29,7 +29,7 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod m.controller.appPrefs.systemDarkTheme, showSettingsModal = showSettingsModal, editColor = { name, initialColor -> - ModalManager.shared.showModalCloseable { close -> + ModalManager.start.showModalCloseable { close -> ColorEditor(name, initialColor, close) } }, From ae9b83515c55224264853de9d2bb0c8aa5ca37af Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:49:35 +0400 Subject: [PATCH 18/18] core: group snd status (#2763) * core: group snd status * schema, implementation * refactor direct, tests * configure, tests * item info * refactor * refactor * remove do * rename * remove receipts on events * refactor * refactor * refactor * refactor * tests * rename tests * aggregates * fix name * refactor --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/ios/SimpleXChat/APITypes.swift | 2 +- .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 143 +++++-- src/Simplex/Chat/Controller.hs | 2 + src/Simplex/Chat/Messages.hs | 71 +++- .../M20230721_group_snd_item_statuses.hs | 33 ++ src/Simplex/Chat/Migrations/chat_schema.sql | 14 + src/Simplex/Chat/Store/Groups.hs | 15 + src/Simplex/Chat/Store/Messages.hs | 77 ++++ src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/Store/Profiles.hs | 13 +- src/Simplex/Chat/Store/Shared.hs | 1 + src/Simplex/Chat/Types.hs | 5 +- src/Simplex/Chat/View.hs | 11 +- tests/ChatTests/Direct.hs | 10 +- tests/ChatTests/Groups.hs | 392 +++++++++++++----- tests/ChatTests/Utils.hs | 13 + tests/MobileTests.hs | 10 +- 19 files changed, 635 insertions(+), 184 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20230721_group_snd_item_statuses.hs diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index ea16af7157..344359b38c 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -127,7 +127,7 @@ public enum ChatCommand { case let .setAllContactReceipts(enable): return "/set receipts all \(onOff(enable))" case let .apiSetUserContactReceipts(userId, userMsgReceiptSettings): let umrs = userMsgReceiptSettings - return "/_set receipts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" + return "/_set receipts contacts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))" case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))" case let .apiMuteUser(userId): return "/_mute user \(userId)" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 3bfe32c732..0e2f53a581 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1882,7 +1882,7 @@ sealed class CC { is SetAllContactReceipts -> "/set receipts all ${onOff(enable)}" is ApiSetUserContactReceipts -> { val mrs = userMsgReceiptSettings - "/_set receipts $userId ${onOff(mrs.enable)} clear_overrides=${onOff(mrs.clearOverrides)}" + "/_set receipts contacts $userId ${onOff(mrs.enable)} clear_overrides=${onOff(mrs.clearOverrides)}" } is ApiHideUser -> "/_hide user $userId ${json.encodeToString(viewPwd)}" is ApiUnhideUser -> "/_unhide user $userId ${json.encodeToString(viewPwd)}" diff --git a/simplex-chat.cabal b/simplex-chat.cabal index bba4d69689..cad28ffe73 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -105,6 +105,7 @@ library Simplex.Chat.Migrations.M20230618_favorite_chats Simplex.Chat.Migrations.M20230621_chat_item_moderations Simplex.Chat.Migrations.M20230705_delivery_receipts + Simplex.Chat.Migrations.M20230721_group_snd_item_statuses Simplex.Chat.Mobile Simplex.Chat.Mobile.WebRTC Simplex.Chat.Options diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index df8120b9cb..514553a574 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -159,6 +159,9 @@ maxMsgReactions = 3 fixedImagePreview :: ImageData fixedImagePreview = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg==" +smallGroupsRcptsMemLimit :: Int +smallGroupsRcptsMemLimit = 20 + logCfg :: LogConfig logCfg = LogConfig {lc_file = Nothing, lc_stderr = True} @@ -397,6 +400,12 @@ processChatCommand = \case withStore' $ \db -> updateUserContactReceipts db user' settings ok user SetUserContactReceipts settings -> withUser $ \User {userId} -> processChatCommand $ APISetUserContactReceipts userId settings + APISetUserGroupReceipts userId' settings -> withUser $ \user -> do + user' <- privateGetUser userId' + validateUserPassword user user' Nothing + withStore' $ \db -> updateUserGroupReceipts db user' settings + ok user + SetUserGroupReceipts settings -> withUser $ \User {userId} -> processChatCommand $ APISetUserGroupReceipts userId settings APIHideUser userId' (UserPwd viewPwd) -> withUser $ \user -> do user' <- privateGetUser userId' case viewPwdHash user' of @@ -494,10 +503,16 @@ processChatCommand = \case chatItems <- withStore $ \db -> getAllChatItems db user pagination search pure $ CRChatItems user chatItems APIGetChatItemInfo chatRef itemId -> withUser $ \user -> do - (aci@(AChatItem _ _ _ ci), versions) <- withStore $ \db -> + (aci@(AChatItem cType dir _ ci), versions) <- withStore $ \db -> (,) <$> getAChatItem db user chatRef itemId <*> liftIO (getChatItemVersions db itemId) let itemVersions = if null versions then maybeToList $ mkItemVersion ci else versions - pure $ CRChatItemInfo user aci ChatItemInfo {itemVersions} + memberDeliveryStatuses <- case (cType, dir) of + (SCTGroup, SMDSnd) -> do + withStore' (`getGroupSndStatuses` itemId) >>= \case + [] -> pure Nothing + memStatuses -> pure $ Just $ map (uncurry MemberDeliveryStatus) memStatuses + _ -> pure Nothing + pure $ CRChatItemInfo user aci ChatItemInfo {itemVersions, memberDeliveryStatuses} APISendMessage (ChatRef cType chatId) live itemTTL (ComposedMessage file_ quotedItemId_ mc) -> withUser $ \user@User {userId} -> withChatLock "sendMessage" $ case cType of CTDirect -> do ct@Contact {contactId, localDisplayName = c, contactUsed} <- withStore $ \db -> getContact db user chatId @@ -572,9 +587,12 @@ processChatCommand = \case (fInv_, ciFile_, ft_) <- unzipMaybe3 <$> setupSndFileTransfer g (length $ filter memberCurrent ms) timed_ <- sndGroupCITimed live gInfo itemTTL (msgContainer, quotedItem_) <- prepareMsg fInv_ timed_ membership - msg@SndMessage {sharedMsgId} <- sendGroupMessage user gInfo ms (XMsgNew msgContainer) + (msg@SndMessage {sharedMsgId}, sentToMembers) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer) mapM_ (sendGroupFileInline ms sharedMsgId) ft_ ci <- saveSndChatItem' user (CDGroupSnd gInfo) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live + withStore' $ \db -> + forM_ sentToMembers $ \GroupMember {groupMemberId} -> + createGroupSndStatus db (chatItemId' ci) groupMemberId CISSndNew forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) setActive $ ActiveG gName @@ -708,7 +726,7 @@ processChatCommand = \case let changed = mc /= oldMC if changed || fromMaybe False itemLive then do - SndMessage {msgId} <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) + (SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) ci' <- withStore' $ \db -> do currentTs <- liftIO getCurrentTime when changed $ @@ -742,7 +760,7 @@ processChatCommand = \case (CIDMInternal, _, _, _) -> deleteGroupCI user gInfo ci True False Nothing =<< liftIO getCurrentTime (CIDMBroadcast, SMDSnd, Just itemSharedMId, True) -> do assertUserGroupRole gInfo GRObserver -- can still delete messages sent earlier - SndMessage {msgId} <- sendGroupMessage user gInfo ms $ XMsgDel itemSharedMId Nothing + (SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms $ XMsgDel itemSharedMId Nothing delGroupChatItem user gInfo ci msgId Nothing (CIDMBroadcast, _, _, _) -> throwChatError CEInvalidChatItemDelete CTContactRequest -> pure $ chatCmdError (Just user) "not supported" @@ -754,7 +772,7 @@ processChatCommand = \case (CIGroupRcv GroupMember {groupMemberId, memberRole, memberId}, Just itemSharedMId) -> do when (groupMemberId /= mId) $ throwChatError CEInvalidChatItemDelete assertUserGroupRole gInfo $ max GRAdmin memberRole - SndMessage {msgId} <- sendGroupMessage user gInfo ms $ XMsgDel itemSharedMId $ Just memberId + (SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms $ XMsgDel itemSharedMId $ Just memberId delGroupChatItem user gInfo ci msgId (Just membership) (_, _) -> throwChatError CEInvalidChatItemDelete APIChatItemReaction (ChatRef cType chatId) itemId add reaction -> withUser $ \user -> withChatLock "chatItemReaction" $ case cType of @@ -786,7 +804,7 @@ processChatCommand = \case let GroupMember {memberId = itemMemberId} = chatItemMember g ci rs <- withStore' $ \db -> getGroupReactions db g membership itemMemberId itemSharedMId True checkReactionAllowed rs - SndMessage {msgId} <- sendGroupMessage user g ms (XMsgReact itemSharedMId (Just itemMemberId) reaction add) + (SndMessage {msgId}, _) <- sendGroupMessage user g ms (XMsgReact itemSharedMId (Just itemMemberId) reaction add) createdAt <- liftIO getCurrentTime reactions <- withStore' $ \db -> do setGroupReaction db g membership itemMemberId itemSharedMId True reaction add msgId createdAt @@ -1409,7 +1427,7 @@ processChatCommand = \case (Just ct, Just cReq) -> sendGrpInvitation user ct gInfo (m :: GroupMember) {memberRole = memRole} cReq _ -> throwChatError $ CEGroupCantResendInvitation gInfo cName _ -> do - msg <- sendGroupMessage user gInfo members $ XGrpMemRole mId memRole + (msg, _) <- sendGroupMessage user gInfo members $ XGrpMemRole mId memRole ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent gEvent) toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) pure CRMemberRoleUser {user, groupInfo = gInfo, member = m {memberRole = memRole}, fromRole = mRole, toRole = memRole} @@ -1425,7 +1443,7 @@ processChatCommand = \case deleteMemberConnection user m withStore' $ \db -> deleteGroupMember db user m _ -> do - msg <- sendGroupMessage user gInfo members $ XGrpMemDel mId + (msg, _) <- sendGroupMessage user gInfo members $ XGrpMemDel mId ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent $ SGEMemberDeleted memberId (fromLocalProfile memberProfile)) toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) deleteMemberConnection user m @@ -1435,7 +1453,7 @@ processChatCommand = \case APILeaveGroup groupId -> withUser $ \user@User {userId} -> do Group gInfo@GroupInfo {membership} members <- withStore $ \db -> getGroup db user groupId withChatLock "leaveGroup" . procCmd $ do - msg <- sendGroupMessage user gInfo members XGrpLeave + (msg, _) <- sendGroupMessage user gInfo members XGrpLeave ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent SGEUserLeft) toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) -- TODO delete direct connections that were unused @@ -1823,7 +1841,7 @@ processChatCommand = \case runUpdateGroupProfile user (Group g@GroupInfo {groupProfile = p} ms) p' = do assertUserGroupRole g GROwner g' <- withStore $ \db -> updateGroupProfile db user g p' - msg <- sendGroupMessage user g' ms (XGrpInfo p') + (msg, _) <- sendGroupMessage user g' ms (XGrpInfo p') let cd = CDGroupSnd g' unless (sameGroupProfileInfo p p') $ do ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p') @@ -2871,12 +2889,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do SENT msgId -> do sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId - withStore' (\db -> getDirectChatItemByAgentMsgId db user contactId connId msgId) >>= \case - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _}}) -> pure () - Just (CChatItem SMDSnd ci) -> do - chatItem <- withStore $ \db -> updateDirectChatItemStatus db user contactId (chatItemId' ci) CISSndSent - toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem) - _ -> pure () + updateDirectItemStatus ct conn msgId $ CISSndSent SSPComplete SWITCH qd phase cStats -> do toView $ CRContactSwitch user ct (SwitchProgress qd phase cStats) when (phase `elem` [SPStarted, SPCompleted]) $ case qd of @@ -2917,10 +2930,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do withCompletedCommand conn agentMsg $ \CommandData {cmdFunction, cmdId} -> when (cmdFunction == CFAckMessage) $ ackMsgDeliveryEvent conn cmdId MERR msgId err -> do - chatItemId_ <- withStore' $ \db -> getChatItemIdByAgentMsgId db connId msgId - forM_ chatItemId_ $ \chatItemId -> do - chatItem <- withStore $ \db -> updateDirectChatItemStatus db user contactId chatItemId (agentErrToItemStatus err) - toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem) + updateDirectItemStatus ct conn msgId $ agentErrToItemStatus err toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) incAuthErrCounter connEntity conn err ERR err -> do @@ -3066,7 +3076,12 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do XGrpInfo p' -> xGrpInfo gInfo m p' msg msgMeta BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta _ -> messageError $ "unsupported message: " <> T.pack (show event) - pure False -- no receipts in group now $ hasDeliveryReceipt $ toCMEventTag event + currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo + let GroupInfo {chatSettings = ChatSettings {sendRcpts}} = gInfo + pure $ + fromMaybe (sendRcptsSmallGroups user) sendRcpts + && hasDeliveryReceipt (toCMEventTag event) + && currentMemCount <= smallGroupsRcptsMemLimit where canSend a | memberRole (m :: GroupMember) <= GRObserver = messageError "member is not allowed to send messages" @@ -3077,6 +3092,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do SENT msgId -> do sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId + updateGroupItemStatus gInfo m conn msgId $ CISSndSent SSPComplete SWITCH qd phase cStats -> do toView $ CRGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) when (phase `elem` [SPStarted, SPCompleted]) $ case qd of @@ -3113,7 +3129,11 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do -- [async agent commands] continuation on receiving OK withCompletedCommand conn agentMsg $ \CommandData {cmdFunction, cmdId} -> when (cmdFunction == CFAckMessage) $ ackMsgDeliveryEvent conn cmdId - MERR _ err -> do + MERR msgId err -> do + chatItemId_ <- withStore' $ \db -> getChatItemIdByAgentMsgId db connId msgId + forM_ chatItemId_ $ \itemId -> do + let GroupMember {groupMemberId} = m + updateGroupMemSndStatus itemId groupMemberId $ agentErrToItemStatus err -- group errors are silenced to reduce load on UI event log -- toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) incAuthErrCounter connEntity conn err @@ -3368,7 +3388,6 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do -- 1) retry processing several times -- 2) stabilize database -- 3) show screen of death to the user asking to restart - -- TODO send receipt depending on contact/group settings tryChatError action >>= \case Right withRcpt -> ack $ if withRcpt then Just "" else Nothing Left e -> ack Nothing >> throwError e @@ -4295,21 +4314,52 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do createGroupFeatureChangedItems user cd CIRcvGroupFeature g g' directMsgReceived :: Contact -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> m () - directMsgReceived ct@Contact {contactId} Connection {connId} msgMeta msgRcpts = do + directMsgReceived ct conn@Connection {connId} msgMeta msgRcpts = do checkIntegrityCreateItem (CDDirectRcv ct) msgMeta forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore $ \db -> createSndMsgDeliveryEvent db connId agentMsgId $ MDSSndRcvd msgRcptStatus - withStore' (\db -> getDirectChatItemByAgentMsgId db user contactId connId agentMsgId) >>= \case - Just (CChatItem SMDSnd ci) -> do - chatItem <- withStore $ \db -> updateDirectChatItemStatus db user contactId (chatItemId' ci) $ CISSndRcvd msgRcptStatus - toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem) - _ -> pure () + updateDirectItemStatus ct conn agentMsgId $ CISSndRcvd msgRcptStatus SSPComplete groupMsgReceived :: GroupInfo -> GroupMember -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> m () - groupMsgReceived gInfo m Connection {connId} msgMeta msgRcpts = do + groupMsgReceived gInfo m conn@Connection {connId} msgMeta msgRcpts = do checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta - forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> + forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore $ \db -> createSndMsgDeliveryEvent db connId agentMsgId $ MDSSndRcvd msgRcptStatus + updateGroupItemStatus gInfo m conn agentMsgId $ CISSndRcvd msgRcptStatus SSPComplete + + updateDirectItemStatus :: Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> m () + updateDirectItemStatus ct@Contact {contactId} Connection {connId} msgId newStatus = + withStore' (\db -> getDirectChatItemByAgentMsgId db user contactId connId msgId) >>= \case + Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ _}}) -> pure () + Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) + | itemStatus == newStatus -> pure () + | otherwise -> do + chatItem <- withStore $ \db -> updateDirectChatItemStatus db user contactId itemId newStatus + toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) chatItem) + _ -> pure () + + updateGroupMemSndStatus :: ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> m Bool + updateGroupMemSndStatus itemId groupMemberId newStatus = + runExceptT (withStore $ \db -> getGroupSndStatus db itemId groupMemberId) >>= \case + Right (CISSndRcvd _ _) -> pure False + Right memStatus + | memStatus == newStatus -> pure False + | otherwise -> withStore' (\db -> updateGroupSndStatus db itemId groupMemberId newStatus) $> True + _ -> pure False + + updateGroupItemStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> m () + updateGroupItemStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus = + withStore' (\db -> getGroupChatItemByAgentMsgId db user groupId connId msgId) >>= \case + Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure () + Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) -> do + memStatusChanged <- updateGroupMemSndStatus itemId groupMemberId newMemStatus + when memStatusChanged $ do + memStatusCounts <- withStore' (`getGroupSndStatusCounts` itemId) + let newStatus = membersGroupItemStatus memStatusCounts + when (newStatus /= itemStatus) $ do + chatItem <- withStore $ \db -> updateGroupChatItemStatus db user groupId itemId newStatus + toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem) + _ -> pure () parseFileDescription :: (ChatMonad m, FilePartyI p) => Text -> m (ValidFileDescription p) parseFileDescription = @@ -4525,26 +4575,33 @@ deliverMessage conn@Connection {connId} cmEventTag msgBody msgId = do (Just $ "createSndMsgDelivery, sndMsgDelivery: " <> show sndMsgDelivery <> ", msgId: " <> show msgId <> ", cmEventTag: " <> show cmEventTag <> ", msgDeliveryStatus: MDSSndAgent") $ \db -> createSndMsgDelivery db sndMsgDelivery msgId -sendGroupMessage :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m SndMessage +sendGroupMessage :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m (SndMessage, [GroupMember]) sendGroupMessage user GroupInfo {groupId} members chatMsgEvent = sendGroupMessage' user members chatMsgEvent groupId Nothing $ pure () -sendGroupMessage' :: (MsgEncodingI e, ChatMonad m) => User -> [GroupMember] -> ChatMsgEvent e -> Int64 -> Maybe Int64 -> m () -> m SndMessage +sendGroupMessage' :: forall e m. (MsgEncodingI e, ChatMonad m) => User -> [GroupMember] -> ChatMsgEvent e -> Int64 -> Maybe Int64 -> m () -> m (SndMessage, [GroupMember]) sendGroupMessage' user members chatMsgEvent groupId introId_ postDeliver = do msg <- createSndMessage chatMsgEvent (GroupId groupId) -- TODO collect failed deliveries into a single error - forM_ (filter memberCurrent members) $ \m -> - messageMember m msg `catchChatError` (toView . CRChatError (Just user)) - pure msg + rs <- forM (filter memberCurrent members) $ \m -> + messageMember m msg `catchChatError` (\e -> toView (CRChatError (Just user) e) $> Nothing) + let sentToMembers = catMaybes rs + pure (msg, sentToMembers) where + messageMember :: GroupMember -> SndMessage -> m (Maybe GroupMember) messageMember m@GroupMember {groupMemberId} SndMessage {msgId, msgBody} = case memberConn m of - Nothing -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ + Nothing -> do + withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ + pure $ Just m Just conn@Connection {connStatus} - | connDisabled conn || connStatus == ConnDeleted -> pure () + | connDisabled conn || connStatus == ConnDeleted -> pure Nothing | connStatus == ConnSndReady || connStatus == ConnReady -> do let tag = toCMEventTag chatMsgEvent deliverMessage conn tag msgBody msgId >> postDeliver - | otherwise -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ + pure $ Just m + | otherwise -> do + withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ + pure $ Just m sendPendingGroupMessages :: ChatMonad m => User -> GroupMember -> Connection -> m () sendPendingGroupMessages user GroupMember {groupMemberId, localDisplayName} conn = do @@ -4926,8 +4983,10 @@ chatCommandP = "/_user " *> (APISetActiveUser <$> A.decimal <*> optional (A.space *> jsonP)), ("/user " <|> "/u ") *> (SetActiveUser <$> displayName <*> optional (A.space *> pwdP)), "/set receipts all " *> (SetAllContactReceipts <$> onOffP), - "/_set receipts " *> (APISetUserContactReceipts <$> A.decimal <* A.space <*> receiptSettings), - "/set receipts " *> (SetUserContactReceipts <$> receiptSettings), + "/_set receipts contacts " *> (APISetUserContactReceipts <$> A.decimal <* A.space <*> receiptSettings), + "/set receipts contacts " *> (SetUserContactReceipts <$> receiptSettings), + "/_set receipts groups " *> (APISetUserGroupReceipts <$> A.decimal <* A.space <*> receiptSettings), + "/set receipts groups " *> (SetUserGroupReceipts <$> receiptSettings), "/_hide user " *> (APIHideUser <$> A.decimal <* A.space <*> jsonP), "/_unhide user " *> (APIUnhideUser <$> A.decimal <* A.space <*> jsonP), "/_mute user " *> (APIMuteUser <$> A.decimal), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 8091320717..90f90fdcb6 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -203,6 +203,8 @@ data ChatCommand | SetAllContactReceipts Bool | APISetUserContactReceipts UserId UserMsgReceiptSettings | SetUserContactReceipts UserMsgReceiptSettings + | APISetUserGroupReceipts UserId UserMsgReceiptSettings + | SetUserGroupReceipts UserMsgReceiptSettings | APIHideUser UserId UserPwd | APIUnhideUser UserId UserPwd | APIMuteUser UserId diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index f2d6553a5a..daf59d82ac 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -21,7 +21,7 @@ import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Lazy.Char8 as LB import Data.Int (Int64) -import Data.Maybe (isJust, isNothing) +import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) @@ -624,13 +624,15 @@ data CIFileInfo = CIFileInfo data CIStatus (d :: MsgDirection) where CISSndNew :: CIStatus 'MDSnd - CISSndSent :: CIStatus 'MDSnd - CISSndRcvd :: MsgReceiptStatus -> CIStatus 'MDSnd + CISSndSent :: SndCIStatusProgress -> CIStatus 'MDSnd + CISSndRcvd :: MsgReceiptStatus -> SndCIStatusProgress -> CIStatus 'MDSnd CISSndErrorAuth :: CIStatus 'MDSnd CISSndError :: String -> CIStatus 'MDSnd CISRcvNew :: CIStatus 'MDRcv CISRcvRead :: CIStatus 'MDRcv +deriving instance Eq (CIStatus d) + deriving instance Show (CIStatus d) instance ToJSON (CIStatus d) where @@ -639,6 +641,8 @@ instance ToJSON (CIStatus d) where instance MsgDirectionI d => ToField (CIStatus d) where toField = toField . decodeLatin1 . strEncode +instance (Typeable d, MsgDirectionI d) => FromField (CIStatus d) where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8 + instance FromField ACIStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8 data ACIStatus = forall d. MsgDirectionI d => ACIStatus (SMsgDirection d) (CIStatus d) @@ -648,8 +652,8 @@ deriving instance Show ACIStatus instance MsgDirectionI d => StrEncoding (CIStatus d) where strEncode = \case CISSndNew -> "snd_new" - CISSndSent -> "snd_sent" - CISSndRcvd status -> "snd_rcvd " <> strEncode status + CISSndSent sndProgress -> "snd_sent " <> strEncode sndProgress + CISSndRcvd msgRcptStatus sndProgress -> "snd_rcvd " <> strEncode msgRcptStatus <> " " <> strEncode sndProgress CISSndErrorAuth -> "snd_error_auth" CISSndError e -> "snd_error " <> encodeUtf8 (T.pack e) CISRcvNew -> "rcv_new" @@ -661,8 +665,8 @@ instance StrEncoding ACIStatus where strP = A.takeTill (== ' ') >>= \case "snd_new" -> pure $ ACIStatus SMDSnd CISSndNew - "snd_sent" -> pure $ ACIStatus SMDSnd CISSndSent - "snd_rcvd" -> ACIStatus SMDSnd . CISSndRcvd <$> (A.space *> strP) + "snd_sent" -> ACIStatus SMDSnd . CISSndSent <$> ((A.space *> strP) <|> pure SSPComplete) + "snd_rcvd" -> ACIStatus SMDSnd <$> (CISSndRcvd <$> (A.space *> strP) <*> ((A.space *> strP) <|> pure SSPComplete)) "snd_error_auth" -> pure $ ACIStatus SMDSnd CISSndErrorAuth "snd_error" -> ACIStatus SMDSnd . CISSndError . T.unpack . safeDecodeUtf8 <$> (A.space *> A.takeByteString) "rcv_new" -> pure $ ACIStatus SMDRcv CISRcvNew @@ -671,8 +675,8 @@ instance StrEncoding ACIStatus where data JSONCIStatus = JCISSndNew - | JCISSndSent - | JCISSndRcvd {msgRcptStatus :: MsgReceiptStatus} + | JCISSndSent {sndProgress :: SndCIStatusProgress} + | JCISSndRcvd {msgRcptStatus :: MsgReceiptStatus, sndProgress :: SndCIStatusProgress} | JCISSndErrorAuth | JCISSndError {agentError :: String} | JCISRcvNew @@ -686,8 +690,8 @@ instance ToJSON JSONCIStatus where jsonCIStatus :: CIStatus d -> JSONCIStatus jsonCIStatus = \case CISSndNew -> JCISSndNew - CISSndSent -> JCISSndSent - CISSndRcvd ok -> JCISSndRcvd ok + CISSndSent sndProgress -> JCISSndSent sndProgress + CISSndRcvd msgRcptStatus sndProgress -> JCISSndRcvd msgRcptStatus sndProgress CISSndErrorAuth -> JCISSndErrorAuth CISSndError e -> JCISSndError e CISRcvNew -> JCISRcvNew @@ -703,6 +707,40 @@ ciCreateStatus content = case msgDirection @d of SMDSnd -> ciStatusNew SMDRcv -> if ciRequiresAttention content then ciStatusNew else CISRcvRead +membersGroupItemStatus :: [(CIStatus 'MDSnd, Int)] -> CIStatus 'MDSnd +membersGroupItemStatus memStatusCounts + | rcvdOk == total = CISSndRcvd MROk SSPComplete + | rcvdOk + rcvdBad == total = CISSndRcvd MRBadMsgHash SSPComplete + | rcvdBad > 0 = CISSndRcvd MRBadMsgHash SSPPartial + | rcvdOk > 0 = CISSndRcvd MROk SSPPartial + | sent == total = CISSndSent SSPComplete + | sent > 0 = CISSndSent SSPPartial + | otherwise = CISSndNew + where + total = sum $ map snd memStatusCounts + rcvdOk = fromMaybe 0 $ lookup (CISSndRcvd MROk SSPComplete) memStatusCounts + rcvdBad = fromMaybe 0 $ lookup (CISSndRcvd MRBadMsgHash SSPComplete) memStatusCounts + sent = fromMaybe 0 $ lookup (CISSndSent SSPComplete) memStatusCounts + +data SndCIStatusProgress + = SSPPartial + | SSPComplete + deriving (Eq, Show, Generic) + +instance ToJSON SndCIStatusProgress where + toJSON = J.genericToJSON . enumJSON $ dropPrefix "SSP" + toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "SSP" + +instance StrEncoding SndCIStatusProgress where + strEncode = \case + SSPPartial -> "partial" + SSPComplete -> "complete" + strP = + A.takeWhile1 (/= ' ') >>= \case + "partial" -> pure SSPPartial + "complete" -> pure SSPComplete + _ -> fail "bad SndCIStatusProgress" + type ChatItemId = Int64 type ChatItemTs = UTCTime @@ -887,7 +925,8 @@ itemDeletedTs = \case CIModerated ts _ -> ts data ChatItemInfo = ChatItemInfo - { itemVersions :: [ChatItemVersion] + { itemVersions :: [ChatItemVersion], + memberDeliveryStatuses :: Maybe [MemberDeliveryStatus] } deriving (Eq, Show, Generic) @@ -917,6 +956,14 @@ mkItemVersion ChatItem {content, meta} = version <$> ciMsgContent content createdAt = createdAt } +data MemberDeliveryStatus = MemberDeliveryStatus + { groupMemberId :: GroupMemberId, + memberDeliveryStatus :: CIStatus 'MDSnd + } + deriving (Eq, Show, Generic) + +instance ToJSON MemberDeliveryStatus where toEncoding = J.genericToEncoding J.defaultOptions + data CIModeration = CIModeration { moderationId :: Int64, moderatorMember :: GroupMember, diff --git a/src/Simplex/Chat/Migrations/M20230721_group_snd_item_statuses.hs b/src/Simplex/Chat/Migrations/M20230721_group_snd_item_statuses.hs new file mode 100644 index 0000000000..8453da88f5 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20230721_group_snd_item_statuses.hs @@ -0,0 +1,33 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20230721_group_snd_item_statuses where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20230721_group_snd_item_statuses :: Query +m20230721_group_snd_item_statuses = + [sql| +CREATE TABLE group_snd_item_statuses ( + group_snd_item_status_id INTEGER PRIMARY KEY, + chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE, + group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE, + group_snd_item_status TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT(datetime('now')), + updated_at TEXT NOT NULL DEFAULT(datetime('now')) +); + +CREATE INDEX idx_group_snd_item_statuses_chat_item_id ON group_snd_item_statuses(chat_item_id); +CREATE INDEX idx_group_snd_item_statuses_group_member_id ON group_snd_item_statuses(group_member_id); + +UPDATE users SET send_rcpts_small_groups = 1 WHERE send_rcpts_contacts = 1; +|] + +down_m20230721_group_snd_item_statuses :: Query +down_m20230721_group_snd_item_statuses = + [sql| +DROP INDEX idx_group_snd_item_statuses_group_member_id; +DROP INDEX idx_group_snd_item_statuses_chat_item_id; + +DROP TABLE group_snd_item_statuses; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 893d00d345..f925726436 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -496,6 +496,14 @@ CREATE TABLE chat_item_moderations( created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); +CREATE TABLE group_snd_item_statuses( + group_snd_item_status_id INTEGER PRIMARY KEY, + chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE, + group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE, + group_snd_item_status TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT(datetime('now')), + updated_at TEXT NOT NULL DEFAULT(datetime('now')) +); CREATE INDEX contact_profiles_index ON contact_profiles( display_name, full_name @@ -687,3 +695,9 @@ CREATE INDEX idx_chat_item_moderations_group ON chat_item_moderations( item_member_id, shared_msg_id ); +CREATE INDEX idx_group_snd_item_statuses_chat_item_id ON group_snd_item_statuses( + chat_item_id +); +CREATE INDEX idx_group_snd_item_statuses_group_member_id ON group_snd_item_statuses( + group_member_id +); diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index c8b58fa857..c3c62d52da 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups getGroupMemberById, getGroupMembers, getGroupMembersForExpiration, + getGroupCurrentMembersCount, deleteGroupConnectionsAndFiles, deleteGroupItemsAndMembers, deleteGroup, @@ -548,6 +549,20 @@ toContactMember :: User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember toContactMember User {userContactId} (memberRow :. connRow) = (toGroupMember userContactId memberRow) {activeConn = toMaybeConnection connRow} +getGroupCurrentMembersCount :: DB.Connection -> User -> GroupInfo -> IO Int +getGroupCurrentMembersCount db User {userId} GroupInfo {groupId} = do + statuses :: [GroupMemberStatus] <- + map fromOnly + <$> DB.query + db + [sql| + SELECT member_status + FROM group_members + WHERE group_id = ? AND user_id = ? + |] + (groupId, userId) + pure $ length $ filter memberCurrent' statuses + getGroupInvitation :: DB.Connection -> User -> GroupId -> ExceptT StoreError IO ReceivedGroupInvitation getGroupInvitation db user groupId = getConnRec_ user >>= \case diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 29e6e2abc3..b185b3a972 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -44,6 +44,7 @@ module Simplex.Chat.Store.Messages createChatItemVersion, deleteDirectChatItem, markDirectChatItemDeleted, + updateGroupChatItemStatus, updateGroupChatItem, deleteGroupChatItem, updateGroupChatItemModerated, @@ -69,6 +70,7 @@ module Simplex.Chat.Store.Messages getGroupChatItem, getGroupChatItemBySharedMsgId, getGroupMemberCIBySharedMsgId, + getGroupChatItemByAgentMsgId, getGroupMemberChatItemLast, getDirectChatItemIdByText, getDirectChatItemIdByText', @@ -87,6 +89,11 @@ module Simplex.Chat.Store.Messages createCIModeration, getCIModeration, deleteCIModeration, + createGroupSndStatus, + getGroupSndStatus, + updateGroupSndStatus, + getGroupSndStatuses, + getGroupSndStatusCounts, ) where @@ -1325,6 +1332,16 @@ getDirectChatItemIdByText' db User {userId} contactId msg = |] (userId, contactId, msg <> "%") +updateGroupChatItemStatus :: forall d. MsgDirectionI d => DB.Connection -> User -> GroupId -> ChatItemId -> CIStatus d -> ExceptT StoreError IO (ChatItem 'CTGroup d) +updateGroupChatItemStatus db user@User {userId} groupId itemId itemStatus = do + ci <- liftEither . correctDir =<< getGroupChatItem db user groupId itemId + currentTs <- liftIO getCurrentTime + liftIO $ DB.execute db "UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ?" (itemStatus, currentTs, userId, groupId, itemId) + pure ci {meta = (meta ci) {itemStatus}} + where + correctDir :: CChatItem c -> Either StoreError (ChatItem c d) + correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci + updateGroupChatItem :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItem 'CTGroup d -> CIContent d -> Bool -> Maybe MessageId -> IO (ChatItem 'CTGroup d) updateGroupChatItem db user groupId ci newContent live msgId_ = do currentTs <- liftIO getCurrentTime @@ -1434,6 +1451,11 @@ getGroupMemberCIBySharedMsgId db user@User {userId} groupId memberId sharedMsgId (GCUserMember, userId, groupId, memberId, sharedMsgId) getGroupChatItem db user groupId itemId +getGroupChatItemByAgentMsgId :: DB.Connection -> User -> GroupId -> Int64 -> AgentMsgId -> IO (Maybe (CChatItem 'CTGroup)) +getGroupChatItemByAgentMsgId db user groupId connId msgId = do + itemId_ <- getChatItemIdByAgentMsgId db connId msgId + maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupChatItem db user groupId) itemId_ + getGroupChatItem :: DB.Connection -> User -> Int64 -> ChatItemId -> ExceptT StoreError IO (CChatItem 'CTGroup) getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do currentTs <- getCurrentTime @@ -1847,3 +1869,58 @@ deleteCIModeration db GroupInfo {groupId} itemMemberId (Just sharedMsgId) = db "DELETE FROM chat_item_moderations WHERE group_id = ? AND item_member_id = ? AND shared_msg_id = ?" (groupId, itemMemberId, sharedMsgId) + +createGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> IO () +createGroupSndStatus db itemId memberId status = + DB.execute + db + "INSERT INTO group_snd_item_statuses (chat_item_id, group_member_id, group_snd_item_status) VALUES (?,?,?)" + (itemId, memberId, status) + +getGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> ExceptT StoreError IO (CIStatus 'MDSnd) +getGroupSndStatus db itemId memberId = + ExceptT . firstRow fromOnly (SENoGroupSndStatus itemId memberId) $ + DB.query + db + [sql| + SELECT group_snd_item_status + FROM group_snd_item_statuses + WHERE chat_item_id = ? AND group_member_id = ? + LIMIT 1 + |] + (itemId, memberId) + +updateGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> IO () +updateGroupSndStatus db itemId memberId status = do + currentTs <- liftIO getCurrentTime + DB.execute + db + [sql| + UPDATE group_snd_item_statuses + SET group_snd_item_status = ?, updated_at = ? + WHERE chat_item_id = ? AND group_member_id = ? + |] + (status, currentTs, itemId, memberId) + +getGroupSndStatuses :: DB.Connection -> ChatItemId -> IO [(GroupMemberId, CIStatus 'MDSnd)] +getGroupSndStatuses db itemId = + DB.query + db + [sql| + SELECT group_member_id, group_snd_item_status + FROM group_snd_item_statuses + WHERE chat_item_id = ? + |] + (Only itemId) + +getGroupSndStatusCounts :: DB.Connection -> ChatItemId -> IO [(CIStatus 'MDSnd, Int)] +getGroupSndStatusCounts db itemId = + DB.query + db + [sql| + SELECT group_snd_item_status, COUNT(1) + FROM group_snd_item_statuses + WHERE chat_item_id = ? + GROUP BY group_snd_item_status + |] + (Only itemId) diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index f1fd40d438..972f0718eb 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -74,6 +74,7 @@ import Simplex.Chat.Migrations.M20230608_deleted_contacts import Simplex.Chat.Migrations.M20230618_favorite_chats import Simplex.Chat.Migrations.M20230621_chat_item_moderations import Simplex.Chat.Migrations.M20230705_delivery_receipts +import Simplex.Chat.Migrations.M20230721_group_snd_item_statuses import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -147,7 +148,8 @@ schemaMigrations = ("20230608_deleted_contacts", m20230608_deleted_contacts, Just down_m20230608_deleted_contacts), ("20230618_favorite_chats", m20230618_favorite_chats, Just down_m20230618_favorite_chats), ("20230621_chat_item_moderations", m20230621_chat_item_moderations, Just down_m20230621_chat_item_moderations), - ("20230705_delivery_receipts", m20230705_delivery_receipts, Just down_m20230705_delivery_receipts) + ("20230705_delivery_receipts", m20230705_delivery_receipts, Just down_m20230705_delivery_receipts), + ("20230721_group_snd_item_statuses", m20230721_group_snd_item_statuses, Just down_m20230721_group_snd_item_statuses) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 608befb54d..ddbe665d71 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -30,6 +30,7 @@ module Simplex.Chat.Store.Profiles updateUserPrivacy, updateAllContactReceipts, updateUserContactReceipts, + updateUserGroupReceipts, updateUserProfile, setUserProfileContactLink, getUserContactProfiles, @@ -92,7 +93,7 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, when activeUser $ DB.execute_ db "UPDATE users SET active_user = 0" let showNtfs = True sendRcptsContacts = True - sendRcptsSmallGroups = False + sendRcptsSmallGroups = True DB.execute db "INSERT INTO users (agent_user_id, local_display_name, active_user, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,0,?,?,?,?,?)" @@ -222,13 +223,21 @@ updateUserPrivacy db User {userId, showNtfs, viewPwdHash} = updateAllContactReceipts :: DB.Connection -> Bool -> IO () updateAllContactReceipts db onOff = - DB.execute db "UPDATE users SET send_rcpts_contacts = ? WHERE view_pwd_hash IS NULL" (Only onOff) + DB.execute + db + "UPDATE users SET send_rcpts_contacts = ?, send_rcpts_small_groups = ? WHERE view_pwd_hash IS NULL" + (onOff, onOff) updateUserContactReceipts :: DB.Connection -> User -> UserMsgReceiptSettings -> IO () updateUserContactReceipts db User {userId} UserMsgReceiptSettings {enable, clearOverrides} = do DB.execute db "UPDATE users SET send_rcpts_contacts = ? WHERE user_id = ?" (enable, userId) when clearOverrides $ DB.execute_ db "UPDATE contacts SET send_rcpts = NULL" +updateUserGroupReceipts :: DB.Connection -> User -> UserMsgReceiptSettings -> IO () +updateUserGroupReceipts db User {userId} UserMsgReceiptSettings {enable, clearOverrides} = do + DB.execute db "UPDATE users SET send_rcpts_small_groups = ? WHERE user_id = ?" (enable, userId) + when clearOverrides $ DB.execute_ db "UPDATE groups SET send_rcpts = NULL" + updateUserProfile :: DB.Connection -> User -> Profile -> ExceptT StoreError IO User updateUserProfile db user p' | displayName == newName = do diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 6f368a774a..9e4d3c0e02 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -92,6 +92,7 @@ data StoreError | SEGroupLinkNotFound {groupInfo :: GroupInfo} | SEHostMemberIdNotFound {groupId :: Int64} | SEContactNotFoundByFileId {fileId :: FileTransferId} + | SENoGroupSndStatus {itemId :: ChatItemId, groupMemberId :: GroupMemberId} deriving (Show, Exception, Generic) instance ToJSON StoreError where diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 33a6bd0456..9d9791f1a9 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -782,7 +782,10 @@ memberActive m = case memberStatus m of GSMemCreator -> True memberCurrent :: GroupMember -> Bool -memberCurrent m = case memberStatus m of +memberCurrent = memberCurrent' . memberStatus + +memberCurrent' :: GroupMemberStatus -> Bool +memberCurrent' = \case GSMemRemoved -> False GSMemLeft -> False GSMemGroupDeleted -> False diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 0b7db12f94..03b7956274 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -465,12 +465,21 @@ localTs tz ts = do viewChatItemStatusUpdated :: AChatItem -> CurrentTime -> TimeZone -> Bool -> Bool -> [StyledString] viewChatItemStatusUpdated (AChatItem _ _ chat item@ChatItem {meta = CIMeta {itemStatus}}) ts tz testView showReceipts = case itemStatus of - CISSndRcvd rcptStatus -> + CISSndRcvd rcptStatus SSPPartial -> + if testView && showReceipts + then prependFirst (viewDeliveryReceiptPartial rcptStatus <> " ") $ viewChatItem chat item False ts tz + else [] + CISSndRcvd rcptStatus SSPComplete -> if testView && showReceipts then prependFirst (viewDeliveryReceipt rcptStatus <> " ") $ viewChatItem chat item False ts tz else [] _ -> [] +viewDeliveryReceiptPartial :: MsgReceiptStatus -> StyledString +viewDeliveryReceiptPartial = \case + MROk -> "%" + MRBadMsgHash -> ttyError' "%!" + viewDeliveryReceipt :: MsgReceiptStatus -> StyledString viewDeliveryReceipt = \case MROk -> "⩗" diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 7d4774500f..7b93b97589 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -2213,13 +2213,13 @@ testConfigureDeliveryReceipts tmp = noReceipt cath alice "4" -- configure receipts for user contacts - alice ##> "/_set receipts 1 on" + alice ##> "/_set receipts contacts 1 on" alice <## "ok" receipt bob alice "5" receipt cath alice "6" -- configure receipts for user contacts (terminal api) - alice ##> "/set receipts off" + alice ##> "/set receipts contacts off" alice <## "ok" noReceipt bob alice "7" noReceipt cath alice "8" @@ -2231,18 +2231,18 @@ testConfigureDeliveryReceipts tmp = noReceipt cath alice "10" -- configure receipts for user contacts (don't clear overrides) - alice ##> "/_set receipts 1 off" + alice ##> "/_set receipts contacts 1 off" alice <## "ok" receipt bob alice "11" noReceipt cath alice "12" - alice ##> "/_set receipts 1 off clear_overrides=off" + alice ##> "/_set receipts contacts 1 off clear_overrides=off" alice <## "ok" receipt bob alice "13" noReceipt cath alice "14" -- configure receipts for user contacts (clear overrides) - alice ##> "/set receipts off clear_overrides=on" + alice ##> "/set receipts contacts off clear_overrides=on" alice <## "ok" noReceipt bob alice "15" noReceipt cath alice "16" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index bb556062cf..6e1e761208 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -59,8 +59,11 @@ chatGroupTests = do it "show message decryption error" testGroupMsgDecryptError it "should report ratchet de-synchronization, synchronize ratchets" testGroupSyncRatchet it "synchronize ratchets, reset connection code" testGroupSyncRatchetCodeReset - describe "message reactions" $ do + describe "group message reactions" $ do it "set group message reactions" testSetGroupMessageReactions + describe "group delivery receipts" $ do + it "should send delivery receipts in group" testSendGroupDeliveryReceipts + it "should send delivery receipts in group depending on configuration" testConfigureGroupDeliveryReceipts testGroup :: HasCallStack => SpecWith FilePath testGroup = versionTestMatrix3 runTestGroup @@ -198,6 +201,7 @@ testGroupShared alice bob cath checkMessages = do alice @@@ [("@cath", "sent invitation to join group team as admin"), ("#team", "received")] bob @@@ [("@alice", "received invitation to join group team as admin"), ("@cath", "hey"), ("#team", "received")] -- test clearing chat + threadDelay 1000000 alice #$> ("/clear #team", id, "#team: all messages are removed locally ONLY") alice #$> ("/_get chat #1 count=100", chat, []) bob #$> ("/clear #team", id, "#team: all messages are removed locally ONLY") @@ -976,6 +980,7 @@ testGroupMessageDelete = (bob <# "#team alice> hello!") (cath <# "#team alice> hello!") + threadDelay 1000000 msgItemId1 <- lastItemId alice alice #$> ("/_delete item #1 " <> msgItemId1 <> " internal", id, "message deleted") @@ -2197,47 +2202,46 @@ testGroupLinkLeaveDelete = testGroupMsgDecryptError :: HasCallStack => FilePath -> IO () testGroupMsgDecryptError tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do - withNewTestChat tmp "cath" cathProfile $ \cath -> do - withNewTestChat tmp "bob" bobProfile $ \bob -> do - createGroup3 "team" alice bob cath - alice #> "#team hi" - [bob, cath] *<# "#team alice> hi" - bob #> "#team hey" - [alice, cath] *<# "#team bob> hey" - setupDesynchronizedRatchet tmp alice cath - withTestChat tmp "bob" $ \bob -> do - bob <## "2 contacts connected (use /cs for the list)" - bob <## "#team: connected to server(s)" - alice #> "#team hello again" - bob <# "#team alice> skipped message ID 8..10" - [bob, cath] *<# "#team alice> hello again" - bob #> "#team received!" - alice <# "#team bob> received!" - cath <# "#team bob> received!" + withNewTestChat tmp "bob" bobProfile $ \bob -> do + createGroup2 "team" alice bob + alice #> "#team hi" + bob <# "#team alice> hi" + bob #> "#team hey" + alice <# "#team bob> hey" + setupDesynchronizedRatchet tmp alice + withTestChat tmp "bob" $ \bob -> do + bob <## "1 contacts connected (use /cs for the list)" + bob <## "#team: connected to server(s)" + alice #> "#team hello again" + bob <# "#team alice> skipped message ID 10..12" + bob <# "#team alice> hello again" + bob #> "#team received!" + alice <# "#team bob> received!" -setupDesynchronizedRatchet :: HasCallStack => FilePath -> TestCC -> TestCC -> IO () -setupDesynchronizedRatchet tmp alice cath = do +setupDesynchronizedRatchet :: HasCallStack => FilePath -> TestCC -> IO () +setupDesynchronizedRatchet tmp alice = do copyDb "bob" "bob_old" withTestChat tmp "bob" $ \bob -> do - bob <## "2 contacts connected (use /cs for the list)" + bob <## "1 contacts connected (use /cs for the list)" bob <## "#team: connected to server(s)" - alice #> "#team hello" - [bob, cath] *<# "#team alice> hello" - bob #> "#team hello too" - [alice, cath] *<# "#team bob> hello too" + alice #> "#team 1" + bob <# "#team alice> 1" + bob #> "#team 2" + alice <# "#team bob> 2" + alice #> "#team 3" + bob <# "#team alice> 3" + bob #> "#team 4" + alice <# "#team bob> 4" withTestChat tmp "bob_old" $ \bob -> do - bob <## "2 contacts connected (use /cs for the list)" + bob <## "1 contacts connected (use /cs for the list)" bob <## "#team: connected to server(s)" bob ##> "/sync #team alice" bob <## "error: command is prohibited" alice #> "#team 1" bob <## "#team alice: decryption error (connection out of sync), synchronization required" bob <## "use /sync #team alice to synchronize" - cath <# "#team alice> 1" alice #> "#team 2" - cath <# "#team alice> 2" alice #> "#team 3" - cath <# "#team alice> 3" (bob "/tail #team 1" bob <# "#team alice> decryption error, possibly due to the device change (header, 3 messages)" @@ -2249,99 +2253,82 @@ setupDesynchronizedRatchet tmp alice cath = do testGroupSyncRatchet :: HasCallStack => FilePath -> IO () testGroupSyncRatchet tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do - withNewTestChat tmp "cath" cathProfile $ \cath -> do - withNewTestChat tmp "bob" bobProfile $ \bob -> do - createGroup3 "team" alice bob cath - alice #> "#team hi" - [bob, cath] *<# "#team alice> hi" - bob #> "#team hey" - [alice, cath] *<# "#team bob> hey" - setupDesynchronizedRatchet tmp alice cath - withTestChat tmp "bob_old" $ \bob -> do - bob <## "2 contacts connected (use /cs for the list)" - bob <## "#team: connected to server(s)" - -- cath and bob are not fully de-synchronized - bob `send` "#team 1" - bob <## "error: command is prohibited" -- silence? - bob <# "#team 1" - (alice "#team 1" - [alice, bob] *<# "#team cath> 1" - bob `send` "#team 2" - bob <## "error: command is prohibited" - bob <# "#team 2" - cath <# "#team bob> incorrect message hash" - cath <# "#team bob> 2" - bob `send` "#team 3" - bob <## "error: command is prohibited" - bob <# "#team 3" - cath <# "#team bob> 3" - -- synchronize bob and alice - bob ##> "/sync #team alice" - bob <## "connection synchronization started" - alice <## "#team bob: connection synchronization agreed" - bob <## "#team alice: connection synchronization agreed" - alice <## "#team bob: connection synchronized" - bob <## "#team alice: connection synchronized" + withNewTestChat tmp "bob" bobProfile $ \bob -> do + createGroup2 "team" alice bob + alice #> "#team hi" + bob <# "#team alice> hi" + bob #> "#team hey" + alice <# "#team bob> hey" + setupDesynchronizedRatchet tmp alice + withTestChat tmp "bob_old" $ \bob -> do + bob <## "1 contacts connected (use /cs for the list)" + bob <## "#team: connected to server(s)" + bob `send` "#team 1" + bob <## "error: command is prohibited" -- silence? + bob <# "#team 1" + (alice "/sync #team alice" + bob <## "connection synchronization started" + alice <## "#team bob: connection synchronization agreed" + bob <## "#team alice: connection synchronization agreed" + alice <## "#team bob: connection synchronized" + bob <## "#team alice: connection synchronized" - bob #$> ("/_get chat #1 count=3", chat, [(1, "connection synchronization started for alice"), (0, "connection synchronization agreed"), (0, "connection synchronized")]) - alice #$> ("/_get chat #1 count=2", chat, [(0, "connection synchronization agreed"), (0, "connection synchronized")]) + bob #$> ("/_get chat #1 count=3", chat, [(1, "connection synchronization started for alice"), (0, "connection synchronization agreed"), (0, "connection synchronized")]) + alice #$> ("/_get chat #1 count=2", chat, [(0, "connection synchronization agreed"), (0, "connection synchronized")]) - alice #> "#team hello again" - [bob, cath] *<# "#team alice> hello again" - bob #> "#team received!" - alice <# "#team bob> received!" - cath <# "#team bob> received!" + alice #> "#team hello again" + bob <# "#team alice> hello again" + bob #> "#team received!" + alice <# "#team bob> received!" testGroupSyncRatchetCodeReset :: HasCallStack => FilePath -> IO () testGroupSyncRatchetCodeReset tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do - withNewTestChat tmp "cath" cathProfile $ \cath -> do - withNewTestChat tmp "bob" bobProfile $ \bob -> do - createGroup3 "team" alice bob cath - alice #> "#team hi" - [bob, cath] *<# "#team alice> hi" - bob #> "#team hey" - [alice, cath] *<# "#team bob> hey" - -- connection not verified - bob ##> "/i #team alice" - aliceInfo bob - bob <## "connection not verified, use /code command to see security code" - -- verify connection - alice ##> "/code #team bob" - bCode <- getTermLine alice - bob ##> ("/verify #team alice " <> bCode) - bob <## "connection verified" - -- connection verified - bob ##> "/i #team alice" - aliceInfo bob - bob <## "connection verified" - setupDesynchronizedRatchet tmp alice cath - withTestChat tmp "bob_old" $ \bob -> do - bob <## "2 contacts connected (use /cs for the list)" - bob <## "#team: connected to server(s)" - bob ##> "/sync #team alice" - bob <## "connection synchronization started" - alice <## "#team bob: connection synchronization agreed" - bob <## "#team alice: connection synchronization agreed" - bob <## "#team alice: security code changed" - alice <## "#team bob: connection synchronized" - bob <## "#team alice: connection synchronized" + withNewTestChat tmp "bob" bobProfile $ \bob -> do + createGroup2 "team" alice bob + alice #> "#team hi" + bob <# "#team alice> hi" + bob #> "#team hey" + alice <# "#team bob> hey" + -- connection not verified + bob ##> "/i #team alice" + aliceInfo bob + bob <## "connection not verified, use /code command to see security code" + -- verify connection + alice ##> "/code #team bob" + bCode <- getTermLine alice + bob ##> ("/verify #team alice " <> bCode) + bob <## "connection verified" + -- connection verified + bob ##> "/i #team alice" + aliceInfo bob + bob <## "connection verified" + setupDesynchronizedRatchet tmp alice + withTestChat tmp "bob_old" $ \bob -> do + bob <## "1 contacts connected (use /cs for the list)" + bob <## "#team: connected to server(s)" + bob ##> "/sync #team alice" + bob <## "connection synchronization started" + alice <## "#team bob: connection synchronization agreed" + bob <## "#team alice: connection synchronization agreed" + bob <## "#team alice: security code changed" + alice <## "#team bob: connection synchronized" + bob <## "#team alice: connection synchronized" - bob #$> ("/_get chat #1 count=4", chat, [(1, "connection synchronization started for alice"), (0, "connection synchronization agreed"), (0, "security code changed"), (0, "connection synchronized")]) - alice #$> ("/_get chat #1 count=2", chat, [(0, "connection synchronization agreed"), (0, "connection synchronized")]) + bob #$> ("/_get chat #1 count=4", chat, [(1, "connection synchronization started for alice"), (0, "connection synchronization agreed"), (0, "security code changed"), (0, "connection synchronized")]) + alice #$> ("/_get chat #1 count=2", chat, [(0, "connection synchronization agreed"), (0, "connection synchronized")]) - -- connection not verified - bob ##> "/i #team alice" - aliceInfo bob - bob <## "connection not verified, use /code command to see security code" + -- connection not verified + bob ##> "/i #team alice" + aliceInfo bob + bob <## "connection not verified, use /code command to see security code" - alice #> "#team hello again" - [bob, cath] *<# "#team alice> hello again" - bob #> "#team received!" - alice <# "#team bob> received!" - (cath "#team hello again" + bob <# "#team alice> hello again" + bob #> "#team received!" + alice <# "#team bob> received!" where aliceInfo :: HasCallStack => TestCC -> IO () aliceInfo bob = do @@ -2418,3 +2405,182 @@ testSetGroupMessageReactions = cath ##> "/tail #team 1" cath <# "#team alice> hi" cath <## " 👍 1" + +testSendGroupDeliveryReceipts :: HasCallStack => FilePath -> IO () +testSendGroupDeliveryReceipts tmp = + withNewTestChatCfg tmp cfg "alice" aliceProfile $ \alice -> do + withNewTestChatCfg tmp cfg "bob" bobProfile $ \bob -> do + withNewTestChatCfg tmp cfg "cath" cathProfile $ \cath -> do + -- turn off contacts receipts for tests + alice ##> "/_set receipts contacts 1 off" + alice <## "ok" + bob ##> "/_set receipts contacts 1 off" + bob <## "ok" + cath ##> "/_set receipts contacts 1 off" + cath <## "ok" + + createGroup3 "team" alice bob cath + threadDelay 1000000 + + alice #> "#team hi" + bob <# "#team alice> hi" + cath <# "#team alice> hi" + alice % "#team hi" + alice ⩗ "#team hi" + + bob #> "#team hey" + alice <# "#team bob> hey" + cath <# "#team bob> hey" + bob % "#team hey" + bob ⩗ "#team hey" + where + cfg = testCfg {showReceipts = True} + +testConfigureGroupDeliveryReceipts :: HasCallStack => FilePath -> IO () +testConfigureGroupDeliveryReceipts tmp = + withNewTestChatCfg tmp cfg "alice" aliceProfile $ \alice -> do + withNewTestChatCfg tmp cfg "bob" bobProfile $ \bob -> do + withNewTestChatCfg tmp cfg "cath" cathProfile $ \cath -> do + -- turn off contacts receipts for tests + alice ##> "/_set receipts contacts 1 off" + alice <## "ok" + bob ##> "/_set receipts contacts 1 off" + bob <## "ok" + cath ##> "/_set receipts contacts 1 off" + cath <## "ok" + + -- create group 1 + createGroup3 "team" alice bob cath + threadDelay 1000000 + + -- create group 2 + alice ##> "/g club" + alice <## "group #club is created" + alice <## "to add members use /a club or /create link #club" + alice ##> "/a club bob" + concurrentlyN_ + [ alice <## "invitation to join the group #club sent to bob", + do + bob <## "#club: alice invites you to join the group as admin" + bob <## "use /j club to accept" + ] + bob ##> "/j club" + concurrently_ + (alice <## "#club: bob joined the group") + (bob <## "#club: you joined the group") + alice ##> "/a club cath" + concurrentlyN_ + [ alice <## "invitation to join the group #club sent to cath", + do + cath <## "#club: alice invites you to join the group as admin" + cath <## "use /j club to accept" + ] + cath ##> "/j club" + concurrentlyN_ + [ alice <## "#club: cath joined the group", + do + cath <## "#club: you joined the group" + cath <## "#club: member bob_1 (Bob) is connected" + cath <## "contact bob_1 is merged into bob" + cath <## "use @bob to send messages", + do + bob <## "#club: alice added cath_1 (Catherine) to the group (connecting...)" + bob <## "#club: new member cath_1 is connected" + bob <## "contact cath_1 is merged into cath" + bob <## "use @cath to send messages" + ] + threadDelay 1000000 + + -- for new users receipts are enabled by default + receipt bob alice cath "team" "1" + receipt bob alice cath "club" "2" + + -- configure receipts in all chats + alice ##> "/set receipts all off" + alice <## "ok" + partialReceipt bob alice cath "team" "3" + partialReceipt bob alice cath "club" "4" + + -- configure receipts for user groups + alice ##> "/_set receipts groups 1 on" + alice <## "ok" + receipt bob alice cath "team" "5" + receipt bob alice cath "club" "6" + + -- configure receipts for user groups (terminal api) + alice ##> "/set receipts groups off" + alice <## "ok" + partialReceipt bob alice cath "team" "7" + partialReceipt bob alice cath "club" "8" + + -- configure receipts for group + alice ##> "/receipts #team on" + alice <## "ok" + receipt bob alice cath "team" "9" + partialReceipt bob alice cath "club" "10" + + -- configure receipts for user groups (don't clear overrides) + alice ##> "/_set receipts groups 1 off" + alice <## "ok" + receipt bob alice cath "team" "11" + partialReceipt bob alice cath "club" "12" + + alice ##> "/_set receipts groups 1 off clear_overrides=off" + alice <## "ok" + receipt bob alice cath "team" "13" + partialReceipt bob alice cath "club" "14" + + -- configure receipts for user groups (clear overrides) + alice ##> "/set receipts groups off clear_overrides=on" + alice <## "ok" + partialReceipt bob alice cath "team" "15" + partialReceipt bob alice cath "club" "16" + + -- configure receipts for group, reset to default + alice ##> "/receipts #team on" + alice <## "ok" + receipt bob alice cath "team" "17" + partialReceipt bob alice cath "club" "18" + + alice ##> "/receipts #team default" + alice <## "ok" + partialReceipt bob alice cath "team" "19" + partialReceipt bob alice cath "club" "20" + + -- cath - disable receipts for user groups + cath ##> "/_set receipts groups 1 off" + cath <## "ok" + noReceipt bob alice cath "team" "21" + noReceipt bob alice cath "club" "22" + + -- partial, all receipts in one group; no receipts in other group + cath ##> "/receipts #team on" + cath <## "ok" + partialReceipt bob alice cath "team" "23" + noReceipt bob alice cath "club" "24" + + alice ##> "/receipts #team on" + alice <## "ok" + receipt bob alice cath "team" "25" + noReceipt bob alice cath "club" "26" + where + cfg = testCfg {showReceipts = True} + receipt cc1 cc2 cc3 gName msg = do + name1 <- userName cc1 + cc1 #> ("#" <> gName <> " " <> msg) + cc2 <# ("#" <> gName <> " " <> name1 <> "> " <> msg) + cc3 <# ("#" <> gName <> " " <> name1 <> "> " <> msg) + cc1 % ("#" <> gName <> " " <> msg) + cc1 ⩗ ("#" <> gName <> " " <> msg) + partialReceipt cc1 cc2 cc3 gName msg = do + name1 <- userName cc1 + cc1 #> ("#" <> gName <> " " <> msg) + cc2 <# ("#" <> gName <> " " <> name1 <> "> " <> msg) + cc3 <# ("#" <> gName <> " " <> name1 <> "> " <> msg) + cc1 % ("#" <> gName <> " " <> msg) + noReceipt cc1 cc2 cc3 gName msg = do + name1 <- userName cc1 + cc1 #> ("#" <> gName <> " " <> msg) + cc2 <# ("#" <> gName <> " " <> name1 <> "> " <> msg) + cc3 <# ("#" <> gName <> " " <> name1 <> "> " <> msg) + cc1 getTermLine cc) `shouldReturn` "i " <> line (⩗) :: HasCallStack => TestCC -> String -> Expectation cc ⩗ line = (dropTime . dropReceipt <$> getTermLine cc) `shouldReturn` line +(%) :: HasCallStack => TestCC -> String -> Expectation +cc % line = (dropTime . dropPartialReceipt <$> getTermLine cc) `shouldReturn` line + ( TestCC -> Expectation ( Just text _ -> Nothing +dropPartialReceipt :: HasCallStack => String -> String +dropPartialReceipt msg = fromMaybe err $ dropPartialReceipt_ msg + where + err = error $ "invalid partial receipt: " <> msg + +dropPartialReceipt_ :: String -> Maybe String +dropPartialReceipt_ msg = case splitAt 2 msg of + ("% ", text) -> Just text + _ -> Nothing + getInvitation :: HasCallStack => TestCC -> IO String getInvitation cc = do cc <## "pass this invitation link to your contact (via another channel):" diff --git a/tests/MobileTests.hs b/tests/MobileTests.hs index 220c8a0d3e..31c0803547 100644 --- a/tests/MobileTests.hs +++ b/tests/MobileTests.hs @@ -27,16 +27,16 @@ noActiveUser = "{\"resp\":{\"type\":\"chatCmdError\",\"chatError\":{\"type\":\"e activeUserExists :: String #if defined(darwin_HOST_OS) && defined(swiftJSON) -activeUserExists = "{\"resp\":{\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":false},\"chatError\":{\"error\":{\"errorType\":{\"userExists\":{\"contactName\":\"alice\"}}}}}}}" +activeUserExists = "{\"resp\":{\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"error\":{\"errorType\":{\"userExists\":{\"contactName\":\"alice\"}}}}}}}" #else -activeUserExists = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":false},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" +activeUserExists = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" #endif activeUser :: String #if defined(darwin_HOST_OS) && defined(swiftJSON) -activeUser = "{\"resp\":{\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":false}}}}" +activeUser = "{\"resp\":{\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" #else -activeUser = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":false}}}" +activeUser = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" #endif chatStarted :: String @@ -75,7 +75,7 @@ pendingSubSummary = "{\"resp\":{\"type\":\"pendingSubSummary\"," <> userJSON <> #endif userJSON :: String -userJSON = "\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":false}" +userJSON = "\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" parsedMarkdown :: String #if defined(darwin_HOST_OS) && defined(swiftJSON)

C>3~6WQKPo#E4BGwgWX_v* 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 Z+F?J3%93A8#H*K|w79%jnTSE){{cl}Wjz1@ literal 0 HcmV?d00001 diff --git a/website/src/_includes/blog_previews/20230422.html b/website/src/_includes/blog_previews/20230422.html index afb6cf757e..d95ef0e9ec 100644 --- a/website/src/_includes/blog_previews/20230422.html +++ b/website/src/_includes/blog_previews/20230422.html @@ -1,4 +1,4 @@ -