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] 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 5d54c5f7b..a1b90e48d 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 bbe4e8318..417dfbbaa 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 76604ac13..63a67737b 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 6b43cd603..3b8902b58 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 15720dcd0..af50e6b05 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 135ab0b66..8624daa5e 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 031a54426..5752d3fd6 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 4d8900964..000000000 --- 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 7d3c592ab..000000000 --- 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 c0065b3e2..000000000 --- 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 d2ffc507e..104b654c5 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 7081acf02..000000000 --- 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 94cecd172..4e7ac583a 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 092f87690..5b45a2317 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 000000000..e237272eb --- /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 61a1f2e83..5a644466a 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 314032984..25369ffdf 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 4fc20c504..39b62a1c8 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 000000000..dfaeac695 --- /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 1099b2fb2..dc6c53ecb 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 000000000..522d86159 --- /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 756604604..4140cd19a 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 000000000..aca8efcb6 --- /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 000000000..f4f748ef0 --- /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 000000000..f0268219d --- /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 11305db50..e6c6ebad0 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 01387dd96..c24ade47d 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 000000000..2604e0d26 --- /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 36559d71f..811974b2d 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 4197cffb0..c458561f9 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 fcaa6158d..984f83d45 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 299156ecd..056ae7495 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 2310479ce..a549d985a 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 a8223582a..719dfeea5 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 d7ef0895a..79361dc07 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 eac0bc3b8..8fb2263c7 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 3c3337db8..25cbffb65 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 585c1eb22..f2f3e2776 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 ef0eb89c8..c05672864 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 861f2b8f6..d23ee58db 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 000000000..0b99e0758 --- /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 000000000..dc56ead54 --- /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 301ae120e..95dac842f 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 6eed47b80..b238bdf7c 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 a36bf42de..23bd5491f 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 000000000..eee525827 --- /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 e0ee94419..7fb6445d5 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 dd159a4c9..ee825730c 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 0d8ac2ef1..0944ff4aa 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 fba7ecc8a..e03e19858 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 6b2cc49b0..3c1771c8b 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 443bce6dc..a1b7b3141 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 4b39102f9..49a29cf14 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 8b2977a3e..fe8dcbd53 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 e772adffa..2f7299393 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 2079abed0..aee62177a 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 7cd680592..c66eeefd5 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 2ad06648c..00a140874 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 737344be1..279f84f85 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 000000000..6e410cc2c --- /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 000000000..6096cc395 --- /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 c4e6d043c..8762c1aec 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 000000000..5af941fb4 --- /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 000000000..7cfda123c --- /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 000000000..e73786b2e --- /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 000000000..a1b39527d --- /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 000000000..a5c455d98 --- /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 000000000..14d6f1701 --- /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 000000000..1a722950c --- /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 000000000..eeea2d08f --- /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 000000000..6d60703e5 --- /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 000000000..be2c87a2f --- /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 000000000..3d31faaa8 --- /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 000000000..03ad4b544 --- /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 000000000..38629f038 --- /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 000000000..bde9d8a49 --- /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 84686d748..a01fc9de5 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 79ab4eead..293257c09 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 75aa950d2..3f106bd72 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 03d4af64c..17ff695e7 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 e20c7dbbd..9acfffb3a 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 000000000..e83db9952 --- /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 9491ea75e..cf9a8dfb6 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 5b305d6a1..b2692d541 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 57ca136cf..bea208809 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 ae5962348..f6b3c899a 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 000000000..2f4ffbb83 --- /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 532ecda9c..447236286 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 fbd35d71a..3e14414fc 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 dca2a04e5..6cb7b4b47 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 c0a63b6dd..1701c6987 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 1e8e31360..b0846cc04 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 8043b7c8e..83076f885 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 6f8e89722..906065f74 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 f5c47fc13..c16a252a9 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 6ea0b345f..99d7de96b 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 046571e00..465603d40 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 a92db078c..9a4d4c377 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 a8f88d7ed..91fb4a6e8 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 e644cfde1..df76617a2 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 390b83993..58e88ec12 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 8a6068103..afe26f8f8 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 a2c8d2168..d93e69728 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 ce1efdeaf..22b9c8a93 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 c1bc15dbd..53d98bfa1 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 1801a0ff7..d95d2a4cf 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 1be6cca41..ac13dd483 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 2e04171ea..3be54376d 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 b2f2245d2..3e4dce7f4 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 752cf8681..e919ab1aa 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 32933a57d..09d0bd4d0 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 cdb261d58..1a23dfc49 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 21e6346ec..6207c1648 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 d66dc4ae2..6ee29b830 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 35bc7ad55..db772d9dc 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 dc0ef7f41..92baa6a97 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 9404b2436..dd7d2ca55 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 80e4ea15b..e0aa04afb 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 6a421c634..650c52569 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 de1cd602d..cb557b051 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 2e2c1676e..2fe7d94f6 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 49a9ce28e..3d8d9f794 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 86fffc6ae..d99de4019 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 f85db8910..18a14efe3 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 d39baa29e..270c671fc 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 77a0219ca..7cd996ec2 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 22192a369..8ced77289 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 de5fd1594..07c5db4d7 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 94f963208..0505f58dc 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 4f3294706..41298b052 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 116106853..9c372020f 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 efa51a379..04e0724c2 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 39c0b583a..7931034de 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 8d9522673..6ec03ad7f 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 515614485..b9b8ea54b 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 3a3f929fc..b3daa610e 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 5395c73f3..e8fbb6663 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 e7782f4d4..fe2a48c1a 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 07be9dcef..137d7f6fd 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 7dabbfde1..ab29816c8 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 b01ccd985..2b86d67e1 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 15f507af6..6a400295e 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 37bf8dd9d..3a799eddf 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 38b2f0e21..5eee8d99d 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 b7d42f493..0877cb42e 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 1ed7d3a95..e3ce25bd3 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 32c52dc1d..fb1df940d 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 9fdf2b396..351a7e9d4 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 f200d4ab6..4c7481ec5 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 538ce4d86..65eb11321 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 000000000..7e916e0ea --- /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 7db325a11..0ae69d82f 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 613caed54..75abc67b4 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 44a56c441..0162ac7e7 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 cad5d4c53..67f82e527 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 eb563dbd0..2f24ff414 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 daf323dc0..4df48ca0a 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 000000000..308038ec5 --- /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 3f7135309..3d59aac44 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 3b1250a75..b6b512e03 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 724f0907f..598ceaab8 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 f60d644a8..6990a69eb 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 a7afb9441..4b6c70df4 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 5c7a40e28..377eb7a8a 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 6202ded6d..5ab0e68c6 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 f0cdfaec8..7ebbb7e5c 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 1f04855cb..74bee1f11 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 f3281b38e..0719c5da3 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 42186f20c..3e35bfb80 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 98a880b4f..ad7c26deb 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 5bac848f7..18437dbf9 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 dd1963921..2913f6ac7 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 fb987be6b..e7de6154e 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 7b54b7ba2..c89e0e43f 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 15ada1e10..5418ddc02 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 95501c2a8..07d937823 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 b0b7b923d..3d6cfb2f9 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 fd7edeaa2..32d598651 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 f7f1770ab..ba8b8df54 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 0d48a68ab..9882caa55 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 000000000..66ba595e1 --- /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 9c37a3420..b6bcf9674 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 6c632160d..b8eb064b8 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 c8bc7b5ed..a506c8daf 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 ac37eff8d..e3190f875 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 b76629952..af640d5b4 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 9c47931a4..ddb1e81ec 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 923b47eb8..38e7d8f98 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 0c2341c69..863266b38 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 5fe8ddc31..e1e35ace8 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 38d6a88eb..d4219a51c 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 a3e51c1da..c69ba2093 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 2a736bf38..c5bde4494 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 8c266b897..215899d0f 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 b7aeb0a80..4728c0f2b 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 aa405ef15..ab2df2014 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 72292d80b..344d002aa 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 af58e98df..6c59bb150 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 c6da840f2..91a1a2ec8 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 3ec12a00e..9c2c95e03 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 a711275cc..f3896a3de 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 6443bfc3f..3d4487160 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 f79b968b1..7cb30440d 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 c80c69ab9..02582ec93 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 7c689c71a..3226dc85b 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 de08ffda6..276c59543 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 9b60ff465..d907e0fff 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 9a4dca412..7f8edbd0f 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 db3174ad9..b78878785 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 e7eab4799..852841477 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 000000000..ffe9bd81a --- /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 000000000..826d705e4 --- /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 000000000..7c64cd469 --- /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 000000000..870fde945 --- /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 000000000..990cff580 --- /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 000000000..2d6011c22 --- /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 000000000..395754c51 --- /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 000000000..72e163c2e --- /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 000000000..40f89d17f --- /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 000000000..13dd65782 --- /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 000000000..7148f8217 --- /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 000000000..7e2ab9d5d --- /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 000000000..1bedcb6f4 --- /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 000000000..84e24a1d5 --- /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 000000000..1ea5018fc --- /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 000000000..0c40c1c35 --- /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 000000000..902675236 --- /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 000000000..df45a8acb --- /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 000000000..c7eeef341 --- /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 000000000..7ea2ef536 --- /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 000000000..05ff1d73d --- /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 000000000..a9c7f5f0a --- /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 000000000..aac995e48 --- /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 000000000..6f4878cfe --- /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 000000000..e4e483092 --- /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 000000000..ade674dff --- /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 000000000..e47fa4ae8 --- /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 000000000..d82462744 --- /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 000000000..a251b7dc2 --- /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 000000000..36aef2437 --- /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 000000000..ae5cd24ce --- /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 000000000..16d35b5b8 --- /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 000000000..66f2d5c6d --- /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 000000000..f770de904 --- /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 000000000..851cf137d --- /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 000000000..a51fd20c5 --- /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 000000000..464c28631 --- /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 000000000..95a079fe1 --- /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 000000000..a004998e0 --- /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() +}