From 1110a78e0615e51024395e582df83548609e2f00 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 26 Feb 2022 13:10:37 +0000 Subject: [PATCH 01/27] update versions/build #s: ios 0.4 (14), android 0.3 (4) --- apps/android/app/build.gradle | 4 ++-- .../chat/simplex/app/views/usersettings/MarkdownHelpView.kt | 2 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index b660ad0476..30811d9b05 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "chat.simplex.app" minSdk 26 targetSdk 32 - versionCode 3 - versionName "0.2" + versionCode 4 + versionName "0.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt index d7e647e042..ec33dca2ad 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt @@ -60,7 +60,7 @@ fun MarkdownHelpLayout(back: () -> Unit) { }) } Row { - MdSyntax("#secret") + MdSyntax("#secret#") SelectionContainer { Text(buildAnnotatedString { withStyle(Format.Secret().style) { append("secret text") } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index dafbc7e23d..617f9d6479 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -855,7 +855,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; From 0413865a3b761b336c96c6594e1f11ad1d63d54b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 26 Feb 2022 20:21:32 +0000 Subject: [PATCH 02/27] ios, core: fix add contact screen, add logging, additional chat events (#380) * ios, core: fix add contact screen, add logging, additional chat events * fix alert dialogues * fix precedence parsing error * update alert messages --- .../java/chat/simplex/app/model/SimpleXAPI.kt | 8 ++-- apps/ios/Shared/ContentView.swift | 37 +++++++++---------- apps/ios/Shared/Model/SimpleXAPI.swift | 10 ++++- .../Shared/Views/ChatList/ChatListView.swift | 2 +- .../Shared/Views/NewChat/AddContactView.swift | 4 +- .../Shared/Views/NewChat/NewChatButton.swift | 20 +++++----- src/Simplex/Chat.hs | 9 ++++- src/Simplex/Chat/Controller.hs | 12 ++++++ src/Simplex/Chat/View.hs | 9 ++++- 9 files changed, 72 insertions(+), 39 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index c8f09cfe90..613003c036 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -268,9 +268,8 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert // if chatModel.upsertChatItem(cInfo, cItem) { // NtfManager.shared.notifyMessageReceived(cInfo, cItem) // } -// default: -// logger.debug("unsupported event: \(res.responseType)") -// } + else -> + Log.d("SIMPLEX" , "unsupported event: ${r.responseType}") } } @@ -394,6 +393,7 @@ class APIResponse(val resp: CR, val corr: String? = null) { sealed class CR { @Serializable @SerialName("activeUser") class ActiveUser(val user: User): CR() @Serializable @SerialName("chatStarted") class ChatStarted: CR() + @Serializable @SerialName("chatRunning") class ChatRunning: CR() @Serializable @SerialName("apiChats") class ApiChats(val chats: List): CR() @Serializable @SerialName("apiChat") class ApiChat(val chat: Chat): CR() @Serializable @SerialName("invitation") class Invitation(val connReqInvitation: String): CR() @@ -430,6 +430,7 @@ sealed class CR { val responseType: String get() = when(this) { is ActiveUser -> "activeUser" is ChatStarted -> "chatStarted" + is ChatRunning -> "chatRunning" is ApiChats -> "apiChats" is ApiChat -> "apiChats" is Invitation -> "invitation" @@ -467,6 +468,7 @@ sealed class CR { val details: String get() = when(this) { is ActiveUser -> json.encodeToString(user) is ChatStarted -> noDetails() + is ChatRunning -> noDetails() is ApiChats -> json.encodeToString(chats) is ApiChat -> json.encodeToString(chat) is Invitation -> connReqInvitation diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 36f233972c..ab891a6613 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -13,32 +13,31 @@ struct ContentView: View { @State private var showNotificationAlert = false var body: some View { - ZStack { - if let user = chatModel.currentUser { - ChatListView(user: user) - .onAppear { - do { - try apiStartChat() - chatModel.chats = try apiGetChats() - } catch { - fatalError("Failed to start or load chats: \(error)") - } - ChatReceiver.shared.start() - NtfManager.shared.requestAuthorization(onDeny: { - alertManager.showAlert(notificationAlert()) - }) + if let user = chatModel.currentUser { + ChatListView(user: user) + .onAppear { + do { + try apiStartChat() + chatModel.chats = try apiGetChats() + } catch { + fatalError("Failed to start or load chats: \(error)") } - } else { - WelcomeView() - } + ChatReceiver.shared.start() + NtfManager.shared.requestAuthorization(onDeny: { + alertManager.showAlert(notificationAlert()) + }) + } + .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } + } else { + WelcomeView() + .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } } - .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } } func notificationAlert() -> Alert { Alert( title: Text("Notification are disabled!"), - message: Text("Please open settings to enable"), + message: Text("The app can notify you when you receive messages or contact requests - please open settings to enable."), primaryButton: .default(Text("Open Settings")) { DispatchQueue.main.async { UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 5717791bb6..16b38c19b9 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -95,6 +95,7 @@ enum ChatResponse: Decodable, Error { case response(type: String, json: String) case activeUser(user: User) case chatStarted + case chatRunning case apiChats(chats: [ChatData]) case apiChat(chat: ChatData) case invitation(connReqInvitation: String) @@ -131,6 +132,7 @@ enum ChatResponse: Decodable, Error { case let .response(type, _): return "* \(type)" case .activeUser: return "activeUser" case .chatStarted: return "chatStarted" + case .chatRunning: return "chatRunning" case .apiChats: return "apiChats" case .apiChat: return "apiChat" case .invitation: return "invitation" @@ -170,6 +172,7 @@ enum ChatResponse: Decodable, Error { case let .response(_, json): return json case let .activeUser(user): return String(describing: user) case .chatStarted: return noDetails + case .chatRunning: return noDetails case let .apiChats(chats): return String(describing: chats) case let .apiChat(chat): return String(describing: chat) case let .invitation(connReqInvitation): return connReqInvitation @@ -306,8 +309,8 @@ func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) async throws -> throw r } -func apiAddContact() async throws -> String { - let r = await chatSendCmd(.addContact) +func apiAddContact() throws -> String { + let r = chatSendCmdSync(.addContact) if case let .invitation(connReqInvitation) = r { return connReqInvitation } throw r } @@ -574,7 +577,10 @@ private func getChatCtrl() -> chat_ctrl { if let controller = chatController { return controller } let dataDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path + "/mobile_v1" var cstr = dataDir.cString(using: .utf8)! + logger.debug("getChatCtrl: chat_init") + ChatModel.shared.terminalItems.append(.cmd(.now, .string("chat_init"))) chatController = chat_init(&cstr) + ChatModel.shared.terminalItems.append(.resp(.now, .response(type: "chat_controller", json: "chat_controller: no details"))) return chatController! } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index fe1e24ea39..766784b0e8 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -84,7 +84,7 @@ struct ChatListView: View { let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") return Alert( title: Text("Connect via \(action) link?"), - message: Text("Your profile will be sent to the contact that you received this link from: \(link)"), + message: Text("Your profile will be sent to the contact that you received this link from"), primaryButton: .default(Text("Connect")) { DispatchQueue.main.async { Task { diff --git a/apps/ios/Shared/Views/NewChat/AddContactView.swift b/apps/ios/Shared/Views/NewChat/AddContactView.swift index f6d62deb26..3c924697ec 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactView.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactView.swift @@ -22,7 +22,9 @@ struct AddContactView: View { .multilineTextAlignment(.center) QRCode(uri: connReqInvitation) .padding() - Text("If you can't show QR code, you can share the invitation link via any channel") + (Text("If you cannot meet in person, you can ") + + Text("scan QR code in the video call").bold() + + Text(", or you can share the invitation link via any other channel.")) .font(.subheadline) .multilineTextAlignment(.center) .padding(.horizontal) diff --git a/apps/ios/Shared/Views/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/NewChat/NewChatButton.swift index db8e1d9f39..d984ddd0df 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatButton.swift @@ -35,18 +35,20 @@ struct NewChatButton: View { } func addContactAction() { - Task { - do { - connReqInvitation = try await apiAddContact() - addContact = true - } catch { - DispatchQueue.global().async { - connectionErrorAlert(error) - } - logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") + do { + connReqInvitation = try apiAddContact() + addContact = true + } catch { + DispatchQueue.global().async { + connectionErrorAlert(error) } + logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") } } + + func addContactSheet() -> some View { + AddContactView(connReqInvitation: connReqInvitation) + } func connectContactSheet() -> some View { ConnectContactView(completed: { err in diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d12a5cfa6e..d5afd8003a 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -144,7 +144,10 @@ processChatCommand = \case user <- withStore $ \st -> createUser st p True atomically . writeTVar u $ Just user pure $ CRActiveUser user - StartChat -> withUser' $ \user -> startChatController user $> CRChatStarted + StartChat -> withUser' $ \user -> + asks agentAsync >>= readTVarIO >>= \case + Just _ -> pure CRChatRunning + _ -> startChatController user $> CRChatStarted APIGetChats -> CRApiChats <$> withUser (\user -> withStore (`getChatPreviews` user)) APIGetChat cType cId pagination -> withUser $ \user -> case cType of CTDirect -> CRApiChat . AChat SCTDirect <$> withStore (\st -> getDirectChat st user cId pagination) @@ -528,7 +531,9 @@ subscribeUserConnections user@User {userId} = do subscribe cId `catchError` (toView . CRRcvFileSubError ft) subscribePendingConnections n = do cs <- withStore (`getPendingConnections` user) - subscribeConns n cs `catchError` \_ -> pure () + summary <- pooledForConcurrentlyN n cs $ \Connection {agentConnId = acId@(AgentConnId cId)} -> + PendingSubStatus acId <$> ((subscribe cId $> Nothing) `catchError` (pure . Just)) + toView $ CRPendingSubSummary summary subscribeUserContactLink n = do cs <- withStore (`getUserContactLinkConnections` userId) (subscribeConns n cs >> toView CRUserContactLinkSubscribed) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 25c7a10270..079d32fb3d 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -133,6 +133,7 @@ data ChatCommand data ChatResponse = CRActiveUser {user :: User} | CRChatStarted + | CRChatRunning | CRApiChats {chats :: [AChat]} | CRApiChat {chat :: AChat} | CRNewChatItem {chatItem :: AChatItem} @@ -204,6 +205,7 @@ data ChatResponse | CRMemberSubError {groupInfo :: GroupInfo, contactName :: ContactName, chatError :: ChatError} -- TODO Contact? or GroupMember? | CRMemberSubErrors {memberSubErrors :: [MemberSubError]} | CRGroupSubscribed {groupInfo :: GroupInfo} + | CRPendingSubSummary {pendingSubStatus :: [PendingSubStatus]} | CRSndFileSubError {sndFileTransfer :: SndFileTransfer, chatError :: ChatError} | CRRcvFileSubError {rcvFileTransfer :: RcvFileTransfer, chatError :: ChatError} | CRUserContactLinkSubscribed @@ -236,6 +238,16 @@ data MemberSubError = MemberSubError instance ToJSON MemberSubError where toEncoding = J.genericToEncoding J.defaultOptions +data PendingSubStatus = PendingSubStatus + { connId :: AgentConnId, + connError :: Maybe ChatError + } + deriving (Show, Generic) + +instance ToJSON PendingSubStatus where + toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} + toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} + data ChatError = ChatError {errorType :: ChatErrorType} | ChatErrorAgent {agentError :: AgentErrorType} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index f0286d7251..e753ed7dfb 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -39,6 +39,7 @@ responseToView :: Bool -> ChatResponse -> [StyledString] responseToView testView = \case CRActiveUser User {profile} -> viewUserProfile profile CRChatStarted -> ["chat started"] + CRChatRunning -> [] CRApiChats chats -> if testView then testViewChats chats else [plain . bshow $ J.encode chats] CRApiChat chat -> if testView then testViewChat chat else [plain . bshow $ J.encode chat] CRNewChatItem (AChatItem _ _ chat item) -> viewChatItem chat item @@ -103,9 +104,9 @@ responseToView testView = \case CRContactSubscribed c -> [ttyContact' c <> ": connected to server"] CRContactSubError c e -> [ttyContact' c <> ": contact error " <> sShow e] CRContactSubSummary summary -> - (if null connected then [] else [sShow (length connected) <> " contacts connected (use " <> highlight' "/cs" <> " for the list)"]) <> viewErrorsSummary errors " contact errors" + (if null subscribed then [] else [sShow (length subscribed) <> " contacts connected (use " <> highlight' "/cs" <> " for the list)"]) <> viewErrorsSummary errors " contact errors" where - (errors, connected) = partition (isJust . contactError) summary + (errors, subscribed) = partition (isJust . contactError) summary CRGroupInvitation GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}} -> [groupInvitation ldn fullName] CRReceivedGroupInvitation g c role -> viewReceivedGroupInvitation g c role @@ -122,6 +123,10 @@ responseToView testView = \case CRMemberSubError g c e -> [ttyGroup' g <> " member " <> ttyContact c <> " error: " <> sShow e] CRMemberSubErrors summary -> viewErrorsSummary summary " group member errors" CRGroupSubscribed g -> [ttyFullGroup g <> ": connected to server(s)"] + CRPendingSubSummary summary -> + (if null subscribed then [] else [sShow (length subscribed) <> " pending connections subscribed"]) <> viewErrorsSummary errors " connection errors" + where + (errors, subscribed) = partition (isJust . connError) summary CRSndFileSubError SndFileTransfer {fileId, fileName} e -> ["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] CRRcvFileSubError RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} e -> From 3f3a503def118ae1aec0e972a5b4825facacef52 Mon Sep 17 00:00:00 2001 From: IanRDavies Date: Sun, 27 Feb 2022 12:14:26 +0000 Subject: [PATCH 03/27] android: notifications (#369) * minimal implementation of notifications and broken framework for background check for messages * linting and need different id to have multiple messages * working notification on new messages * add autocancel to notifications * add rudimentary linking to chat from notification * group notifications from the same chat * clarify comment * revert to working version * refactor * minors * two channels, silent and shouty * rudimentary state control for notifications * check if running in foreground * more elegant solution to don't notify if in chat * tidy up DisposableEffect use * change message notification priority to high * nuke opt-ins * navigation via notification occasionally works with race condition (WIP) * notification navigation is working; remove chat list/view from navigation; refactor ChatListNavLinkView * group all simplex notifications, only show the latest message per chat, notification icons * increase time to 30 sec Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/android/app/build.gradle | 9 ++ .../java/chat/simplex/app/MainActivity.kt | 110 ++++++++---------- .../main/java/chat/simplex/app/SimplexApp.kt | 23 +++- .../java/chat/simplex/app/model/BGManager.kt | 39 +++++++ .../java/chat/simplex/app/model/ChatModel.kt | 5 +- .../java/chat/simplex/app/model/NtfManager.kt | 78 +++++++++++++ .../java/chat/simplex/app/model/SimpleXAPI.kt | 38 ++++-- .../java/chat/simplex/app/views/SplashView.kt | 9 +- .../chat/simplex/app/views/TerminalView.kt | 2 - .../chat/simplex/app/views/WelcomeView.kt | 14 +-- .../simplex/app/views/chat/ChatInfoView.kt | 8 +- .../chat/simplex/app/views/chat/ChatView.kt | 91 +++++++-------- .../simplex/app/views/chat/item/CIMetaView.kt | 1 - .../app/views/chat/item/ChatItemView.kt | 3 - .../app/views/chat/item/TextItemView.kt | 12 +- .../app/views/chatlist/ChatListNavLinkView.kt | 80 +++++-------- .../app/views/chatlist/ChatListView.kt | 40 ++----- .../app/views/chatlist/ChatPreviewView.kt | 3 - .../app/views/helpers/ChatInfoImage.kt | 3 - .../chat/simplex/app/views/helpers/Util.kt | 1 - .../app/views/newchat/ConnectContactView.kt | 3 - .../simplex/app/views/newchat/NewChatSheet.kt | 13 +-- .../views/usersettings/MarkdownHelpView.kt | 3 - .../src/main/res/drawable-hdpi/ntf_icon.png | Bin 0 -> 861 bytes .../src/main/res/drawable-mdpi/ntf_icon.png | Bin 0 -> 569 bytes .../src/main/res/drawable-xhdpi/ntf_icon.png | Bin 0 -> 1089 bytes .../src/main/res/drawable-xxhdpi/ntf_icon.png | Bin 0 -> 1761 bytes .../main/res/drawable-xxxhdpi/ntf_icon.png | Bin 0 -> 2652 bytes apps/android/build.gradle | 6 +- 29 files changed, 319 insertions(+), 275 deletions(-) create mode 100644 apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt create mode 100644 apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt create mode 100644 apps/android/app/src/main/res/drawable-hdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-mdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-xhdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-xxhdpi/ntf_icon.png create mode 100644 apps/android/app/src/main/res/drawable-xxxhdpi/ntf_icon.png diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 30811d9b05..1a91671c05 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -40,6 +40,11 @@ android { } kotlinOptions { jvmTarget = '1.8' + freeCompilerArgs += "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi" + freeCompilerArgs += "-opt-in=androidx.compose.ui.text.ExperimentalTextApi" + freeCompilerArgs += "-opt-in=androidx.compose.material.ExperimentalMaterialApi" + freeCompilerArgs += "-opt-in=com.google.accompanist.insets.ExperimentalAnimatedInsets" + freeCompilerArgs += "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi" } externalNativeBuild { cmake { @@ -73,6 +78,10 @@ dependencies { implementation "androidx.navigation:navigation-compose:2.4.1" implementation "com.google.accompanist:accompanist-insets:0.23.0" + def work_version = "2.7.1" + implementation "androidx.work:work-runtime-ktx:$work_version" + implementation "androidx.work:work-multiprocess:$work_version" + def camerax_version = "1.1.0-beta01" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index a935883631..30ee947668 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -2,15 +2,14 @@ package chat.simplex.app import android.app.Application import android.content.Intent +import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.layout.Box -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.runtime.* import androidx.lifecycle.AndroidViewModel import androidx.navigation.* import androidx.navigation.compose.* @@ -20,26 +19,20 @@ import chat.simplex.app.views.* import chat.simplex.app.views.chat.ChatInfoView import chat.simplex.app.views.chat.ChatView import chat.simplex.app.views.chatlist.ChatListView +import chat.simplex.app.views.chatlist.openChat import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.newchat.* import chat.simplex.app.views.usersettings.* -import com.google.accompanist.insets.ExperimentalAnimatedInsets -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.serialization.decodeFromString -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalAnimatedInsets -@ExperimentalPermissionsApi -@ExperimentalMaterialApi class MainActivity: ComponentActivity() { private val vm by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // testJson() - connectIfOpenedViaUri(intent, vm.chatModel) + processIntent(intent, vm.chatModel) +// vm.app.initiateBackgroundWork() setContent { SimpleXTheme { Navigation(vm.chatModel) @@ -48,50 +41,37 @@ class MainActivity: ComponentActivity() { } } -@DelicateCoroutinesApi class SimplexViewModel(application: Application): AndroidViewModel(application) { - val chatModel = getApplication().chatModel + val app = getApplication() + val chatModel = app.chatModel } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun MainPage(chatModel: ChatModel, nav: NavController) { when (chatModel.userCreated.value) { null -> SplashView() - false -> WelcomeView(chatModel) { nav.navigate(Pages.ChatList.route) } - true -> ChatListView(chatModel, nav) + false -> WelcomeView(chatModel) // { nav.navigate(Pages.ChatList.route) } + true -> if (chatModel.chatId.value == null) { + ChatListView(chatModel, nav) + } else { + ChatView(chatModel, nav) + } } } -@ExperimentalTextApi -@ExperimentalAnimatedInsets -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun Navigation(chatModel: ChatModel) { + println("*** in Navigation") val nav = rememberNavController() - + val scope = rememberCoroutineScope() Box { NavHost(navController = nav, startDestination = Pages.Home.route) { composable(route = Pages.Home.route) { + println("*** composable MainPage") MainPage(chatModel, nav) } composable(route = Pages.Welcome.route) { - WelcomeView(chatModel) { - nav.navigate(Pages.Home.route) { - popUpTo(Pages.Home.route) { inclusive = true } - } - } - } - composable(route = Pages.ChatList.route) { - ChatListView(chatModel, nav) - } - composable(route = Pages.Chat.route) { - ChatView(chatModel, nav) + WelcomeView(chatModel) } composable(route = Pages.AddContact.route) { AddContactView(chatModel, nav) @@ -136,8 +116,6 @@ sealed class Pages(val route: String) { object Terminal: Pages("terminal") object Welcome: Pages("welcome") object TerminalItemDetails: Pages("details") - object ChatList: Pages("chats") - object Chat: Pages("chat") object AddContact: Pages("add_contact") object Connect: Pages("connect") object ChatInfo: Pages("chat_info") @@ -147,28 +125,42 @@ sealed class Pages(val route: String) { object Markdown: Pages("markdown") } -@DelicateCoroutinesApi -fun connectIfOpenedViaUri(intent: Intent?, chatModel: ChatModel) { - val uri = intent?.data - if (intent?.action == "android.intent.action.VIEW" && uri != null) { - Log.d("SIMPLEX", "connectIfOpenedViaUri: opened via link") - if (chatModel.currentUser.value == null) { - chatModel.appOpenUrl.value = uri - } else { - withUriAction(chatModel, uri) { action -> - chatModel.alertManager.showAlertMsg( - title = "Connect via $action link?", - text = "Your profile will be sent to the contact that you received this link from.", - confirmText = "Connect", - onConfirm = { - withApi { - Log.d("SIMPLEX", "connectIfOpenedViaUri: connecting") - connectViaUri(chatModel, action, uri) - } - } - ) +fun processIntent(intent: Intent?, chatModel: ChatModel) { + when (intent?.action) { + NtfManager.OpenChatAction -> { + val chatId = intent.getStringExtra("chatId") + Log.d("SIMPLEX", "processIntent: OpenChatAction $chatId") + if (chatId != null) { + val cInfo = chatModel.getChat(chatId)?.chatInfo + if (cInfo != null) withApi { openChat(chatModel, cInfo) } } } + "android.intent.action.VIEW" -> { + val uri = intent.data + if (uri != null) connectIfOpenedViaUri(uri, chatModel) + } + } +} + +fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { + Log.d("SIMPLEX", "connectIfOpenedViaUri: opened via link") + if (chatModel.currentUser.value == null) { + // TODO open from chat list view + chatModel.appOpenUrl.value = uri + } else { + withUriAction(chatModel, uri) { action -> + chatModel.alertManager.showAlertMsg( + title = "Connect via $action link?", + text = "Your profile will be sent to the contact that you received this link from.", + confirmText = "Connect", + onConfirm = { + withApi { + Log.d("SIMPLEX", "connectIfOpenedViaUri: connecting") + connectViaUri(chatModel, action, uri) + } + } + ) + } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index cb8fa644fe..aa04839b3f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -6,14 +6,14 @@ import android.util.Log import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf -import chat.simplex.app.model.ChatController -import chat.simplex.app.model.ChatModel +import androidx.work.* +import chat.simplex.app.model.* import chat.simplex.app.views.helpers.withApi -import kotlinx.coroutines.DelicateCoroutinesApi import java.io.BufferedReader import java.io.InputStreamReader import java.util.* import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit import kotlin.concurrent.thread // ghc's rts @@ -27,15 +27,28 @@ external fun chatInit(path: String): ChatCtrl external fun chatSendCmd(ctrl: ChatCtrl, msg: String) : String external fun chatRecvMsg(ctrl: ChatCtrl) : String -@DelicateCoroutinesApi class SimplexApp: Application() { private lateinit var controller: ChatController lateinit var chatModel: ChatModel + private lateinit var ntfManager: NtfManager + + fun initiateBackgroundWork() { + val backgroundConstraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + val request = OneTimeWorkRequestBuilder() + .setInitialDelay(5, TimeUnit.MINUTES) + .setConstraints(backgroundConstraints) + .build() + WorkManager.getInstance(applicationContext) + .enqueue(request) + } override fun onCreate() { super.onCreate() + ntfManager = NtfManager(applicationContext) val ctrl = chatInit(applicationContext.filesDir.toString()) - controller = ChatController(ctrl, AlertManager()) + controller = ChatController(ctrl, AlertManager(), ntfManager, applicationContext) chatModel = controller.chatModel withApi { val user = controller.apiGetActiveUser() diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt new file mode 100644 index 0000000000..a511bf06fe --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt @@ -0,0 +1,39 @@ +package chat.simplex.app.model + +import android.content.Context +import android.util.Log +import androidx.work.* +import chat.simplex.app.chatRecvMsg +import java.util.concurrent.TimeUnit + +class BackgroundAPIWorker(appContext: Context, workerParams: WorkerParameters, ctrl: ChatCtrl): + Worker(appContext, workerParams) { + val controller = ctrl + override fun doWork(): Result { + Log.d("BackgroundAPIWorker", "running") + getNewItems() + + // Enqueue another request for later to make this periodic + val request = buildRequest() + WorkManager.getInstance(applicationContext) + .enqueue(request) + + return Result.success() + } + + private fun getNewItems() { + val json = chatRecvMsg(controller) + val r = APIResponse.decodeStr(json).resp + Log.d("SIMPLEX", "chatRecvMsg: ${r.responseType}") + } + + private fun buildRequest(): OneTimeWorkRequest { + val backgroundConstraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + return OneTimeWorkRequestBuilder() + .setInitialDelay(5, TimeUnit.MINUTES) + .setConstraints(backgroundConstraints) + .build() + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 82b1dbf827..726b2fb5d4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -8,18 +8,15 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.* import androidx.compose.ui.text.style.TextDecoration import chat.simplex.app.SimplexApp -import chat.simplex.app.ui.theme.* -import kotlinx.coroutines.DelicateCoroutinesApi +import chat.simplex.app.ui.theme.SecretColor import kotlinx.datetime.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@DelicateCoroutinesApi class ChatModel(val controller: ChatController, val alertManager: SimplexApp.AlertManager) { var currentUser = mutableStateOf(null) var userCreated = mutableStateOf(null) var chats = mutableStateListOf() - var chatsLoaded = mutableStateOf(null) var chatId = mutableStateOf(null) var chatItems = mutableStateListOf() diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt new file mode 100644 index 0000000000..114cf22bf6 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt @@ -0,0 +1,78 @@ +package chat.simplex.app.model + +import android.app.* +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import chat.simplex.app.MainActivity +import chat.simplex.app.R +import kotlinx.datetime.Clock + +class NtfManager(val context: Context) { + companion object { + const val MessageChannel: String = "chat.simplex.app.MESSAGE_NOTIFICATION" + const val MessageGroup: String = "chat.simplex.app.MESSAGE_NOTIFICATION" + const val OpenChatAction: String = "chat.simplex.app.OPEN_CHAT" + } + + private val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private var prevNtfTime = mutableMapOf() + private val msgNtfTimeoutMs = 30000L + + init { + manager.createNotificationChannel(NotificationChannel( + MessageChannel, + "SimpleX Chat messages", + NotificationManager.IMPORTANCE_HIGH + )) + } + + fun notifyMessageReceived(cInfo: ChatInfo, cItem: ChatItem) { + Log.d("SIMPLEX", "notifyMessageReceived ${cInfo.id}") + val now = Clock.System.now().toEpochMilliseconds() + val recentNotification = (now - prevNtfTime.getOrDefault(cInfo.id, 0) < msgNtfTimeoutMs) + prevNtfTime[cInfo.id] = now + + val notification = NotificationCompat.Builder(context, MessageChannel) + .setContentTitle(cInfo.displayName) + .setContentText(cItem.content.text) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setGroup(MessageGroup) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) + .setSmallIcon(R.drawable.ntf_icon) + .setColor(0x88FFFF) + .setAutoCancel(true) + .setContentIntent(getMsgPendingIntent(cInfo)) + .setSilent(recentNotification) + .build() + + val summary = NotificationCompat.Builder(context, MessageChannel) + .setSmallIcon(R.drawable.ntf_icon) + .setColor(0x88FFFF) + .setGroup(MessageGroup) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) + .setGroupSummary(true) + .build() + + with(NotificationManagerCompat.from(context)) { + // using cInfo.id only shows one notification per chat and updates it when the message arrives + notify(cInfo.id.hashCode(), notification) + notify(0, summary) + } + } + + private fun getMsgPendingIntent(cInfo: ChatInfo) : PendingIntent{ + Log.d("SIMPLEX", "getMsgPendingIntent ${cInfo.id}") + val uniqueInt = (System.currentTimeMillis() and 0xfffffff).toInt() + val intent = Intent(context, MainActivity::class.java) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra("chatId", cInfo.id) + .setAction(OpenChatAction) + return TaskStackBuilder.create(context).run { + addNextIntentWithParentStack(intent) + getPendingIntent(uniqueInt, PendingIntent.FLAG_IMMUTABLE) + } + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 613003c036..875b90b1e2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -1,10 +1,14 @@ package chat.simplex.app.model +import android.app.ActivityManager +import android.app.ActivityManager.RunningAppProcessInfo +import android.content.Context import android.util.Log import androidx.compose.runtime.mutableStateOf import chat.simplex.app.* import chat.simplex.app.views.helpers.withApi -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* @@ -14,19 +18,17 @@ import kotlin.concurrent.thread typealias ChatCtrl = Long -@DelicateCoroutinesApi -open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.AlertManager) { +open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.AlertManager, val ntfManager: NtfManager, val appContext: Context) { var chatModel = ChatModel(this, alertManager) suspend fun startChat(u: User) { - chatModel.currentUser = mutableStateOf(u) - chatModel.userCreated.value = true Log.d("SIMPLEX (user)", u.toString()) try { apiStartChat() chatModel.userAddress.value = apiGetUserAddress() chatModel.chats.addAll(apiGetChats()) - chatModel.chatsLoaded.value = true + chatModel.currentUser = mutableStateOf(u) + chatModel.userCreated.value = true startReceiver() Log.d("SIMPLEX", "started chat") } catch(e: Error) { @@ -41,6 +43,18 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert } } + open fun isAppOnForeground(context: Context): Boolean { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val appProcesses = activityManager.runningAppProcesses ?: return false + val packageName = context.packageName + for (appProcess in appProcesses) { + if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName == packageName) { + return true + } + } + return false + } + suspend fun sendCmd(cmd: CC): CR { return withContext(Dispatchers.IO) { val c = cmd.cmdString @@ -146,9 +160,9 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert suspend fun apiDeleteChat(type: ChatType, id: Long): Boolean { val r = sendCmd(CC.ApiDeleteChat(type, id)) - when { - r is CR.ContactDeleted -> return true // TODO groups - r is CR.ChatCmdError -> { + when (r) { + is CR.ContactDeleted -> return true // TODO groups + is CR.ChatCmdError -> { val e = r.chatError if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) { alertManager.showAlertMsg( @@ -260,7 +274,9 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert val cInfo = r.chatItem.chatInfo val cItem = r.chatItem.chatItem chatModel.addChatItem(cInfo, cItem) -// NtfManager.shared.notifyMessageReceived(cInfo, cItem) + if (!isAppOnForeground(appContext) || chatModel.chatId.value != cInfo.id) { + ntfManager.notifyMessageReceived(cInfo, cItem) + } } // case let .chatItemUpdated(aChatItem): // let cInfo = aChatItem.chatInfo @@ -432,7 +448,7 @@ sealed class CR { is ChatStarted -> "chatStarted" is ChatRunning -> "chatRunning" is ApiChats -> "apiChats" - is ApiChat -> "apiChats" + is ApiChat -> "apiChat" is Invitation -> "invitation" is SentConfirmation -> "sentConfirmation" is SentInvitation -> "sentInvitation" diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt index 503a50e211..4fa4b311d3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt @@ -1,16 +1,11 @@ package chat.simplex.app.views -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import chat.simplex.app.R @Composable fun SplashView() { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt index 4d83b38b1d..d12a4a62d9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -23,10 +23,8 @@ import chat.simplex.app.views.helpers.CloseSheetBar import chat.simplex.app.views.helpers.withApi import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsWithImePadding -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.launch -@DelicateCoroutinesApi @Composable fun TerminalView(chatModel: ChatModel, nav: NavController) { TerminalLayout(chatModel.terminalItems, nav::popBackStack, nav::navigate) { cmd -> diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt index fcfafb82d0..bacd1dad29 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt @@ -19,11 +19,9 @@ import chat.simplex.app.model.Profile import chat.simplex.app.views.helpers.withApi import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsWithImePadding -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi @Composable -fun WelcomeView(chatModel: ChatModel, routeHome: () -> Unit) { +fun WelcomeView(chatModel: ChatModel) { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { Box( modifier = Modifier @@ -60,7 +58,7 @@ fun WelcomeView(chatModel: ChatModel, routeHome: () -> Unit) { color = MaterialTheme.colors.onBackground ) Spacer(Modifier.height(24.dp)) - CreateProfilePanel(chatModel, routeHome) + CreateProfilePanel(chatModel) } } } @@ -71,9 +69,8 @@ fun isValidDisplayName(name: String) : Boolean { return (name.firstOrNull { it.isWhitespace() }) == null } -@DelicateCoroutinesApi @Composable -fun CreateProfilePanel(chatModel: ChatModel, routeHome: () -> Unit) { +fun CreateProfilePanel(chatModel: ChatModel) { var displayName by remember { mutableStateOf("") } var fullName by remember { mutableStateOf("") } @@ -154,10 +151,9 @@ fun CreateProfilePanel(chatModel: ChatModel, routeHome: () -> Unit) { Profile(displayName, fullName) ) chatModel.controller.startChat(user) - routeHome() } }, enabled = displayName.isNotEmpty() - ) { Text("Create")} + ) { Text("Create") } } -} \ No newline at end of file +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index 4f1dc1b823..061fab5e3b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -15,19 +15,16 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController -import chat.simplex.app.Pages import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi @Composable fun ChatInfoView(chatModel: ChatModel, nav: NavController) { val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value } if (chat != null) { ChatInfoLayout(chat, - close = { nav.popBackStack() }, + close = nav::popBackStack, deleteContact = { chatModel.alertManager.showAlertMsg( title = "Delete contact?", @@ -39,7 +36,8 @@ fun ChatInfoView(chatModel: ChatModel, nav: NavController) { val r = chatModel.controller.apiDeleteChat(cInfo.chatType, cInfo.apiId) if (r) { chatModel.removeChat(cInfo.id) - nav.navigate(Pages.ChatList.route) + chatModel.chatId.value = null + nav.popBackStack() } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 5b6e3044f5..7270720ebf 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -1,6 +1,8 @@ package chat.simplex.app.views.chat import android.content.res.Configuration +import android.util.Log +import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -12,7 +14,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -24,56 +25,54 @@ import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.item.ChatItemView import chat.simplex.app.views.helpers.ChatInfoImage import chat.simplex.app.views.helpers.withApi -import com.google.accompanist.insets.* -import kotlinx.coroutines.* +import com.google.accompanist.insets.ProvideWindowInsets +import com.google.accompanist.insets.navigationBarsWithImePadding +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.datetime.Clock -@ExperimentalTextApi -@ExperimentalAnimatedInsets -@DelicateCoroutinesApi @Composable fun ChatView(chatModel: ChatModel, nav: NavController) { - if (chatModel.chatId.value != null && chatModel.chats.count() > 0) { - val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } - if (chat != null) { - // TODO a more advanced version would mark as read only if in view - LaunchedEffect(chat.chatItems) { - delay(1000L) - if (chat.chatItems.count() > 0) { - chatModel.markChatItemsRead(chat.chatInfo) - withApi { - chatModel.controller.apiChatRead( - chat.chatInfo.chatType, - chat.chatInfo.apiId, - CC.ItemRange(chat.chatStats.minUnreadItemId, chat.chatItems.last().id) - ) - } + val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } + if (chat == null) { + chatModel.chatId.value = null + } else { + BackHandler { chatModel.chatId.value = null } + // TODO a more advanced version would mark as read only if in view + LaunchedEffect(chat.chatItems) { + Log.d("SIMPLEX", "ChatView ${chatModel.chatId.value}: LaunchedEffect") + delay(1000L) + if (chat.chatItems.count() > 0) { + chatModel.markChatItemsRead(chat.chatInfo) + withApi { + chatModel.controller.apiChatRead( + chat.chatInfo.chatType, + chat.chatInfo.apiId, + CC.ItemRange(chat.chatStats.minUnreadItemId, chat.chatItems.last().id) + ) } } - ChatLayout(chat, chatModel.chatItems, - back = { nav.popBackStack() }, - info = { nav.navigate(Pages.ChatInfo.route) }, - sendMessage = { msg -> - withApi { - // show "in progress" - val cInfo = chat.chatInfo - val newItem = chatModel.controller.apiSendMessage( - type = cInfo.chatType, - id = cInfo.apiId, - mc = MsgContent.MCText(msg) - ) - // hide "in progress" - if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem) - } - } - ) } + ChatLayout(chat, chatModel.chatItems, + back = { chatModel.chatId.value = null }, + info = { nav.navigate(Pages.ChatInfo.route) }, + sendMessage = { msg -> + withApi { + // show "in progress" + val cInfo = chat.chatInfo + val newItem = chatModel.controller.apiSendMessage( + type = cInfo.chatType, + id = cInfo.apiId, + mc = MsgContent.MCText(msg) + ) + // hide "in progress" + if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem) + } + } + ) } } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalAnimatedInsets @Composable fun ChatLayout( chat: Chat, chatItems: List, @@ -101,7 +100,10 @@ fun ChatLayout( @Composable fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit) { - Box(Modifier.height(60.dp).padding(horizontal = 8.dp), + Box( + Modifier + .height(60.dp) + .padding(horizontal = 8.dp), contentAlignment = Alignment.CenterStart ) { IconButton(onClick = back) { @@ -136,9 +138,6 @@ fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit) { } } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalAnimatedInsets @Composable fun ChatItemsList(chatItems: List) { val listState = rememberLazyListState() @@ -157,8 +156,6 @@ fun ChatItemsList(chatItems: List) { } } -@ExperimentalTextApi -@ExperimentalAnimatedInsets @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt index fcbd726a6d..aafcb7d041 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt @@ -1,6 +1,5 @@ package chat.simplex.app.views.chat.item -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt index 8a252f714b..ddcbbdc1a5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.UriHandler -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.model.CIDirection @@ -13,7 +12,6 @@ import chat.simplex.app.model.ChatItem import chat.simplex.app.ui.theme.SimpleXTheme import kotlinx.datetime.Clock -@ExperimentalTextApi @Composable fun ChatItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) { val sent = chatItem.chatDir.sent @@ -33,7 +31,6 @@ fun ChatItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) { } } -@ExperimentalTextApi @Preview @Composable fun PreviewChatItemView() { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt index 5be27d0e57..949648138e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt @@ -1,6 +1,7 @@ package chat.simplex.app.views.chat.item -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.SelectionContainer @@ -15,8 +16,8 @@ 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.model.* -import chat.simplex.app.ui.theme.LightGray +import chat.simplex.app.model.CIDirection +import chat.simplex.app.model.ChatItem import chat.simplex.app.ui.theme.SimpleXTheme import kotlinx.datetime.Clock @@ -24,7 +25,6 @@ import kotlinx.datetime.Clock val SentColorLight = Color(0x1E45B8FF) val ReceivedColorLight = Color(0x1EB1B0B5) -@ExperimentalTextApi @Composable fun TextItemView(chatItem: ChatItem, uriHandler: UriHandler? = null) { val sent = chatItem.chatDir.sent @@ -55,7 +55,6 @@ fun appendGroupMember(b: AnnotatedString.Builder, chatItem: ChatItem, groupMembe } } -@ExperimentalTextApi @Composable fun MarkdownText ( chatItem: ChatItem, @@ -110,7 +109,6 @@ fun MarkdownText ( } } -@ExperimentalTextApi @Preview @Composable fun PreviewTextItemViewSnd() { @@ -123,7 +121,6 @@ fun PreviewTextItemViewSnd() { } } -@ExperimentalTextApi @Preview @Composable fun PreviewTextItemViewRcv() { @@ -136,7 +133,6 @@ fun PreviewTextItemViewRcv() { } } -@ExperimentalTextApi @Preview @Composable fun PreviewTextItemViewLong() { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt index d13f2863ad..9efbb1183a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController @@ -17,41 +16,31 @@ import chat.simplex.app.Pages import chat.simplex.app.model.* import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.withApi -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.datetime.Clock -@ExperimentalTextApi @Composable fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel, nav: NavController) { - ChatListNavLink( + ChatListNavLinkLayout( chat = chat, - action = { - when (chat.chatInfo) { - is ChatInfo.Direct -> chatNavLink(chat, chatModel, nav) - is ChatInfo.Group -> chatNavLink(chat, chatModel, nav) - is ChatInfo.ContactRequest -> contactRequestNavLink(chat.chatInfo, chatModel, nav) + click = { + if (chat.chatInfo is ChatInfo.ContactRequest) { + contactRequestAlertDialog(chat.chatInfo, chatModel, nav) + } else { + withApi { openChat(chatModel, chat.chatInfo) } } } ) } -@DelicateCoroutinesApi -fun chatNavLink(chatPreview: Chat, chatModel: ChatModel, navController: NavController) { - withApi { - val chatInfo = chatPreview.chatInfo - val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId) - if (chat != null) { - chatModel.chatId.value = chatInfo.id - chatModel.chatItems = chat.chatItems.toMutableStateList() - navController.navigate(Pages.Chat.route) - } else { - // TODO show error? or will apiGetChat show it - } +suspend fun openChat(chatModel: ChatModel, cInfo: ChatInfo) { + val chat = chatModel.controller.apiGetChat(cInfo.chatType, cInfo.apiId) + if (chat != null) { + chatModel.chatItems = chat.chatItems.toMutableStateList() + chatModel.chatId.value = cInfo.id } } -@DelicateCoroutinesApi -fun contactRequestNavLink(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel, navController: NavController) { +fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel, navController: NavController) { chatModel.alertManager.showAlertDialog( title = "Accept connection request?", text = "If you choose to reject sender will NOT be notified", @@ -75,27 +64,12 @@ fun contactRequestNavLink(contactRequest: ChatInfo.ContactRequest, chatModel: Ch ) } -@ExperimentalTextApi @Composable -fun ChatListNavLink(chat: Chat, action: () -> Unit) { - ChatListNavLinkLayout( - content = { - when (chat.chatInfo) { - is ChatInfo.Direct -> ChatPreviewView(chat) - is ChatInfo.Group -> ChatPreviewView(chat) - is ChatInfo.ContactRequest -> ContactRequestView(chat) - } - }, - action = action - ) -} - -@Composable -fun ChatListNavLinkLayout(content: (@Composable () -> Unit), action: () -> Unit) { +fun ChatListNavLinkLayout(chat: Chat, click: () -> Unit) { Surface( modifier = Modifier .fillMaxWidth() - .clickable(onClick = action) + .clickable(onClick = click) .height(88.dp) ) { Row( @@ -104,18 +78,18 @@ fun ChatListNavLinkLayout(content: (@Composable () -> Unit), action: () -> Unit) .padding(vertical = 8.dp) .padding(start = 8.dp) .padding(end = 12.dp), - verticalAlignment = Alignment.Top, -// TODO? -// verticalAlignment = Alignment.CenterVertically, -// horizontalArrangement = Arrangement.SpaceEvenly + verticalAlignment = Alignment.Top ) { - content.invoke() + if (chat.chatInfo is ChatInfo.ContactRequest) { + ContactRequestView(chat) + } else { + ChatPreviewView(chat) + } } } Divider(Modifier.padding(horizontal = 8.dp)) } -@ExperimentalTextApi @Preview @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -125,7 +99,7 @@ fun ChatListNavLinkLayout(content: (@Composable () -> Unit), action: () -> Unit) @Composable fun PreviewChatListNavLinkDirect() { SimpleXTheme { - ChatListNavLink( + ChatListNavLinkLayout( chat = Chat( chatInfo = ChatInfo.Direct.sampleData, chatItems = listOf( @@ -138,12 +112,11 @@ fun PreviewChatListNavLinkDirect() { ), chatStats = Chat.ChatStats() ), - action = {} + click = {} ) } } -@ExperimentalTextApi @Preview @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -153,7 +126,7 @@ fun PreviewChatListNavLinkDirect() { @Composable fun PreviewChatListNavLinkGroup() { SimpleXTheme { - ChatListNavLink( + ChatListNavLinkLayout( chat = Chat( chatInfo = ChatInfo.Group.sampleData, chatItems = listOf( @@ -166,12 +139,11 @@ fun PreviewChatListNavLinkGroup() { ), chatStats = Chat.ChatStats() ), - action = {} + click = {} ) } } -@ExperimentalTextApi @Preview @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -181,13 +153,13 @@ fun PreviewChatListNavLinkGroup() { @Composable fun PreviewChatListNavLinkContactRequest() { SimpleXTheme { - ChatListNavLink( + ChatListNavLinkLayout( chat = Chat( chatInfo = ChatInfo.ContactRequest.sampleData, chatItems = listOf(), chatStats = Chat.ChatStats() ), - action = {} + click = {} ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index 179ed1919e..6f06d3e366 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.navigation.NavController @@ -22,10 +21,9 @@ import chat.simplex.app.model.ChatModel import chat.simplex.app.views.chat.ChatHelpView import chat.simplex.app.views.newchat.NewChatSheet import chat.simplex.app.views.usersettings.SettingsView -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch -@ExperimentalMaterialApi class ScaffoldController(val scope: CoroutineScope) { lateinit var state: BottomSheetScaffoldState val expanded = mutableStateOf(false) @@ -49,7 +47,6 @@ class ScaffoldController(val scope: CoroutineScope) { } } -@ExperimentalMaterialApi @Composable fun scaffoldController(): ScaffoldController { val ctrl = ScaffoldController(scope = rememberCoroutineScope()) @@ -64,10 +61,6 @@ fun scaffoldController(): ScaffoldController { return ctrl } -@ExperimentalTextApi -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun ChatListView(chatModel: ChatModel, nav: NavController) { val scaffoldCtrl = scaffoldController() @@ -86,14 +79,11 @@ fun ChatListView(chatModel: ChatModel, nav: NavController) { .background(MaterialTheme.colors.background) ) { ChatListToolbar(scaffoldCtrl) - when (chatModel.chatsLoaded.value) { - true -> if (chatModel.chats.isNotEmpty()) { - ChatList(chatModel, nav) - } else { - val user = chatModel.currentUser.value - Help(scaffoldCtrl, displayName = user?.profile?.displayName) - } - else -> ChatList(chatModel, nav) + if (chatModel.chats.isNotEmpty()) { + ChatList(chatModel, nav) + } else { + val user = chatModel.currentUser.value + Help(scaffoldCtrl, displayName = user?.profile?.displayName) } } if (scaffoldCtrl.expanded.value) { @@ -108,7 +98,6 @@ fun ChatListView(chatModel: ChatModel, nav: NavController) { } } -@ExperimentalMaterialApi @Composable fun Help(scaffoldCtrl: ScaffoldController, displayName: String?) { Column( @@ -142,7 +131,6 @@ fun Help(scaffoldCtrl: ScaffoldController, displayName: String?) { } } -@ExperimentalMaterialApi @Composable fun ChatListToolbar(scaffoldCtrl: ScaffoldController) { Row( @@ -178,8 +166,6 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) { } } -@ExperimentalTextApi -@DelicateCoroutinesApi @Composable fun ChatList(chatModel: ChatModel, navController: NavController) { Divider(Modifier.padding(horizontal = 8.dp)) @@ -191,15 +177,3 @@ fun ChatList(chatModel: ChatModel, navController: NavController) { } } } -//@Preview -//@Composable -//fun PreviewChatListView() { -// SimpleXTheme { -// ChatListView( -// chats = listOf( -// Chat() -// ), -// -// ) -// } -//} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt index caad7bdb1d..95fc05f7c2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt @@ -9,7 +9,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -23,7 +22,6 @@ import chat.simplex.app.views.chat.item.MarkdownText import chat.simplex.app.views.helpers.ChatInfoImage import chat.simplex.app.views.helpers.badgeLayout -@ExperimentalTextApi @Composable fun ChatPreviewView(chat: Chat) { Row { @@ -78,7 +76,6 @@ fun ChatPreviewView(chat: Chat) { } } -@ExperimentalTextApi @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt index 27eac8842b..cdc1f86039 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt @@ -1,7 +1,6 @@ package chat.simplex.app.views.helpers import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons @@ -9,8 +8,6 @@ import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.SupervisedUserCircle import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt index a8f5ec9dd4..8f148fbd3b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -2,6 +2,5 @@ package chat.simplex.app.views.helpers import kotlinx.coroutines.* -@DelicateCoroutinesApi fun withApi(action: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch { withContext(Dispatchers.Main, action) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt index 5a82ea51c9..e9715229f0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt @@ -18,9 +18,7 @@ import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.CloseSheetBar import chat.simplex.app.views.helpers.withApi -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi @Composable fun ConnectContactView(chatModel: ChatModel, nav: NavController) { ConnectContactLayout( @@ -44,7 +42,6 @@ fun ConnectContactView(chatModel: ChatModel, nav: NavController) { ) } -@DelicateCoroutinesApi fun withUriAction( chatModel: ChatModel, uri: Uri, run: suspend (String) -> Unit diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt index 64db15a0cf..4b338f4f3d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt @@ -21,13 +21,8 @@ import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chatlist.ScaffoldController import chat.simplex.app.views.helpers.withApi -import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberPermissionState -import kotlinx.coroutines.DelicateCoroutinesApi -@DelicateCoroutinesApi -@ExperimentalPermissionsApi -@ExperimentalMaterialApi @Composable fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController, nav: NavController) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) @@ -47,15 +42,12 @@ fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController, nav: Nav newChatCtrl.collapse() nav.navigate(Pages.Connect.route) cameraPermissionState.launchPermissionRequest() - }, - close = { - newChatCtrl.collapse() } ) } @Composable -fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit, close: () -> Unit) { +fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) { Row(Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 48.dp), @@ -116,8 +108,7 @@ fun PreviewNewChatSheet() { SimpleXTheme { NewChatSheetLayout( addContact = {}, - scanCode = {}, - close = {}, + scanCode = {} ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt index ec33dca2ad..0e9ec8fb87 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt @@ -7,10 +7,7 @@ import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* import androidx.compose.runtime.Composable 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.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController diff --git a/apps/android/app/src/main/res/drawable-hdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-hdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e17716d5938412ecdcdc63ff5ef87cd58eaaf10c GIT binary patch literal 861 zcmV-j1ETziP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uy~$4Nv%R9Fesn7eLNF%X6W1$luW zBB+sg1=`cnJN1?v6P@FEA(259rRA?b13NA3?t%w-6Lc zgR$LDnA=MSjwYrc9~>d;4|4ltcbw_XzLqYxoy_BK2YT-aS>I)fahVyL2+XC)03ruC z7N~cQl=b)EA7^8;uVA(#Un`Rl;4&0}G6?tU%S`z-2~@PF4CW2YYL*E{O`8upE#D`o zY;O@Xfx&Je)W%+?FHz2k-;mB?-#O#Au)CkYU$&WfI>yjbw=x7-Hhm- zK`M}HU!boY_xF!#&w~XNNq{atv9cW&PXxCh2Y~Hbm?<_ovwP#{2Q>gkjgX=R>IztzM>L({A zyqrs4-Ixgi6Imtkn^|617U^B;_K(&)KctT+JyaH;qg^?it*VUn;1gK43uDU|}9A3O! zeYP>uFN>zf6?VExK11K2ys0W5+GnlD##c%F{P08!$7++!luF1_k z(ViK+XOdlX^=SE;4oe^OGzNz%2|Chl;S&O7nJLwo@uPiO@R}aSDxSK}LNK0(-k{d^ n@0cvup82spf!ZIlITyM=3tYT+9pOe&00000NkvXXu0mjfbCYdK literal 0 HcmV?d00001 diff --git a/apps/android/app/src/main/res/drawable-mdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-mdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f9c5cf450ae43e2e4a13c58eeb4b253889a1938 GIT binary patch literal 569 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY_X?{V~7Xu z(W$oH%#H#M!u5@<60Vjys#6mLH%$CvzuV!|9|q~%Edov(Hg4Xqaoc9GjRN+Ms@tpI zy_G$=&{C%2&bhiXXWpH8S8DZN_4Cg?-0cQUy(WCWwk7h^zF79yyHt$#q2RrTLR{)w z${$MEJz!+>=3OQnG3B6bP17XnhuKf0!ew^1o-L_Wp4)0&z$#Uwo_>GV#M|Y3N6$$~ z`l=^7sJ~O0CQxboc#X-z;{m%vf3bhyah#X&X8FMtm**c8{G%dwG|O<|ymU>iFLHMd zO7CfU#Fv*~Th=63mi_4WQHfJ5TF<@aAJcbe?yG)g%<)U}?H$hhCh_VUn|GbENSHV= z+h=AIpKjpd@RF?CJEwMO%iKtCin%RS%jl{sb)Snn<9%b>T=DZ;FMTTFEB20OyW5#6 z+3kKs+t+2``Gz!s?$nr552JQ%FqJ+3R#x!XZC@ikwFD#Ih{C^_>(}^1@6UK^C)%ym zp3HT9eww0O>eI-`l;veS$}hjh-!1O_$fI?A{=QvZt7}`c7xT@PkaAkR-$yvVw0A+t oU*0CWa-+8Uf4>~|`}gZF)6)GM8GAO&nhlCJPgg&ebxsLQ0PJktQUCw| literal 0 HcmV?d00001 diff --git a/apps/android/app/src/main/res/drawable-xhdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-xhdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..44f7315eeca217d1144f9d635fb72f082cc121cc GIT binary patch literal 1089 zcmV-H1it%;P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFt4TybRA>e5nmcb4F%*Tfp+lmD zmMT)JmFSj&X+jG%@JlFD(LhN@NB#wW1Mw6Jk&t+#03@WaL1_8TYAg*NU(dBQgdHhY zI_$1}ZJ&GnIx}Hw>)a-sfz@ht89W3#C1K#d4Hn=5xKz@1G+ze30-k~2;2pT`=^Vcq zxCOp|B{%>VJ#9zxVL)YGx6s^sY9SybC z-J&Jfm+urRYS)!t=&MbbJ7T-C!|mIf0TnQiH>)+X)zE)7tJD|GRxSu@T{Fh&Jt&q3 zw9yRT16B~PsVm+uxgb&}3uBmeL`_y6P_G~wVC@O2dNz1s<$?%jd#~51p!Ckj?TK#J z?mNQ@LK)L~x0Z|sI|Os!mIwHHWXB8q`ln6Av?nE^`tZ|DE;Mj_y5)qUufB^i3Z}V) zTJqD}#4Y)uWAqb#Da*-@e}9a^Xu1~}^J=D`K$c{IBKLsgs$%DY9O=^%G!w>b{ zhNa(O8wRo@u>v{-MY^e!N0mRMbbo}Y=4;m6%C(A2X(Om==``?5HAB+6ZD=HVv>I5!Y`Is9ym0+m2EmRsIlizdgX$Em5k-)HH%r(c1(3 zT_UlDOH~^P=|a()x7c!T+Z2Q)({)MhSCOe{#9%4eG5Gd?c3~#szn3W?{r8Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>OG!jQRCodHoZW66H4w+!lvm-F zS3m{vA&`&|Dz`z2Dv+Qma0zM@MFpOMeDxW42ksD0fLfjc3LJj_?Rw?iW3OlSI45V5 z9ci??Ue9>uZ+krUp5)G*g%$)B1m=psYPGrxKY}0B_5qzg!S`$H-I_j(7dYR-qQBvf zYc-Cpr|^F*zg0``*7RVw!1)@E=^q|G+{6gvrS~{i%DsS^4@ej*C%%DGBxl~A0upk2 z@RO8M%B1)0DSEdmgOTyH%J|Y0zw0p!iF@#OV+wgEkajyJVc@3)C*s#8kPxtd6ZF31 z=5-JFTd#|Om%&=YbrdAzHLXe`Cp7afH;=aN@RSBI)B{p7CZR*qH5YSu)gw)>FQ(Xf zEixD@=gxaBQ zlP)$yNTcfzp7*pGf)k$Q80<*_sNRwCocS!}!RaYi9DUZgZM)Gbw|hw-Mybc&nF2yd z2ED(+R{Eu+Z_0Zbe`AVVBZIKO(Rohw-b3dPu;R&c*g}6%>66LMBm~i?sV*D*8(f40 zPJCohZT_S81df;T7d%@?8_|yvzPgIgA>ZOSC|m4DC9Q(tVHd@XQ$#r%@n{*8855ugV3juV+BXk=P~>Y zwo(x!6qQC!+*Y$6%mSnr)P5RE6z;Rr4LQrS3hLE!Uk1at_kfh0Zs^hIJ%cUOJ5dO> zB(G>YwrFhYB+p`acjVCVaA*j!mk#;0z>Tcp>4i~;I%w@oYG_mU+$w+KP~O`DAa`Pv zRkZ_nEuK-P5qJ(;NYk^I0`R;Mz#SO58yb*-p7_yh1&|3zAh$)zPEpx~w5G^uQOm0e z%9EO8l53SIn!~0mry&_=1xa)GH(0~fLZjdWBT(cCvdXxBw=~|?$;PkHe({PT4@g$q zOyC6LQ0f86YQyH>Lb#`f@LnT)EvV<(HJ77QtHQCt zfLs+B_7HZ16O2Sw9d!v$C4~X|Y84vxTxmdgk~Pi*N3_axd5)*OR9$p=f^e`wNnrQo zB&D}n84YJ8oeDTbT75~Qu$hqiBe z7ftsMs*iG%YV|nkH8uTV7nb?=s(}O!OQM)VvjwMg5*WtP?b>SKwWgJIAEqGum4N2@ zBW%r+Mz&uGy|Dy0iAlC7l+-G^PM(V^Q}T3u5raLivdqvL)EQ11BM~?EG-cs&uTlLW)-!4PrTxE;>#|xoRgu--FhFtZ> zOXv$H0?)a(&{h2WMdwKZ=YH_neMMLx6+iE~6(fM&dH;ZFiqlpZ!10_0Qoa6;n4)uYSYft!=X|RlEEWe?9Rn@28^L0 zEjXuHSNkvYX-1>OLi<#j_ejD4jT{d+x95*p07fzA47QMRt?r{9_f<|5hNF3Bo^epC zl{Fx1O09iqpzIj7Qu0S!I|Inj5WI?0`I3{#(>#O(vgcH@%2sITOZ8`*@+5(yyK;2x z!Jc#Rs#~I~0B8BL@G(^BZB2{Pc@kB8KLn?~0)A;EDV9y&c#b(%^BqPCr1+?W$ByJg zGUk+QKu*&w7n7vde`HZ=^s3%ttUwwbx@jrj=*LT~-r?#daLOlvSLN(fy{$80x+z2M z@|>wUgB-e)CkdE&f)hESk+l4Sr2xosT@Y9h7$WdLEf6dIq$od)00000NkvXXu0mjf DXT~}I literal 0 HcmV?d00001 diff --git a/apps/android/app/src/main/res/drawable-xxxhdpi/ntf_icon.png b/apps/android/app/src/main/res/drawable-xxxhdpi/ntf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..71e75ec56997e471d8bc3bd37675c15301fc8ef4 GIT binary patch literal 2652 zcmV-i3ZwOjP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91V4wp41ONa40RR91U;qFB0I4%yP5=N2#z{m$RCodHoz0FMH4w+MNPv_B zpEpEkZb%>wAm9O#m*5e2f$#=7ML~c>a@sv0$RV6^;E>(bM!9e#d`OYFu|h{l;nKwFqI`t;_WvQ!S4;qB1nLvImJJS4E;lpyIC@+mpfH@jO z=iUdFLC|ufJ)w7FCXY0K8^n?{U$gYZ9DN;a8FWxH;U^GF+L*UMr-{nD3dq{AX#O?$ zrm6X^Hqg=cz&@~$&Izr<8fiLY?HLMu2+t0ft_kW6fS&2FX2NX{OD(qr1yn#M+*r+5 z{;S}5UmGW%M#tX)zXJ;$xh-In=DY5Nkn*&z$mCov&3Cm50qA`2J%}aEgv&t7PORoj z_+NwP-1e;HGlI|{SZ4yPQKZ3==Fh@!Y8wK17g+WL*n&wLuGakZUUr&-Z+=HG+L*1@ z5v;u=YiB(-+@_#ceMdktQGf0gWn2awvSjDan!if;^*aRh7O?h`0dq-X~CC9lbEvl%~!K)b53QeE$=~Qp;_TB;ggzG_3aY*5{1$n>7lR z=KG=-if(Gu>2ojWHtla{~YjLBNj93sILND?uEO& zv(5y0Q9E7P))Amgnaw`;x@N*Iu+0P*L!uXU$d~P4Ls_~^>5E>_AXsO@7QDTeI-!F> zSXH(Ao0KGLkT%!C8@az$InlN;hXQGddtEyyfu z$G7Z|>?Kuj3z(f-@JjPzqZhO>-v_ZIPiWK~vpXZ;w>S?aqkij%Oq(D?3(2-1vwN2k zB>Wp7wodIsaz|Uv2@l}A1T4`O628@a{-HaVt!E1NSs*0$k_JJe9K7C<_Y9Ec#|j^o z$I%OXTHO{Jgs7Q7etB%IZu4+Uj7lm{+u(c+CJFK?=-rsfBhB9ivD7+?P=M8KA*Bh8 zw$O$fvgW(}v7JRIMKfW~EiK?#8?&sV&Y8fw3S^C5pggPFLL2iI=rmDzCsT7DSV-n? zD~O=A^!O5BtUzu-l^odYf};P$2G%F@RvHb+xm0LCOD}ENmI~Thd}(V5+?PN@a%`4E zZtlsOC4C-jbN9)-l}3X!6RhUqH}Gmd(Iy>3+9XZU3-G0wi<|HdYTmX4?n|Ii8U&e* zLy!h5^?+wNFj@O1Wr;FteKPJ%qY^DgG9P2L8J1v@B_(sV(~|0K=y3+h7$Vw2mQ)rO zM^L}0Of#X+X+{<^<#zIgIL*wBg>egHbMl1_LPe!}Am>cbA?q4Q)_k`UlqnO{vaZ9{ zNjhW|adW%v_dZj()w1d8(RE%FyYi%Lt7X&G-f;JVSArQ5X;}pY_H9|;B8TcL|SG&-PWDbvQtd+-7Zp>^>kae*X~|$8WR*1;qY`lZgjmEVfrC$ z60DTMYpv7Ihf2H-PSO%oM=#XcZ28m@bQyFy@ch!soJs>u+9oV>)Qw&s$xIpqo2N5% zEU35wH^E6VLw(T;biquT37>*tX95+@2eIWblkaGlgBTUfu83vf>q)K%(;aivN<<~I zGfv|6(<740IpZi?!run5YrYOya!21Z@Tya@I~pNq*^wJMR`{?iq8F?dlLjF++G08v zGIYMydxYOjH=H{+$p(Rg|B5V6FZgSuk6mQ&7CiKCrIxEIm_U1uX7` zn580hXtv>z-^fk(CB!BYezNG2C2P)CJ6>IHi)3KV1 znhDlu3;AL!w_Fmw>EHyPF8YfG=cZ)OcS*PWG)ZpbBGC(!+ZAo0_DlG-AL&UTFl!Lz zpQ+Z`(ea6le3n?+&s3XrQGxaZ&4k*$u&}|K`Eq`zt_sv=k$Zf%YW@{?I}cg8+Dskw zSwxzD3)nT^Z4`xRCdfLv2!@Pa(3#{ba35IcbC?FZGNdl;2^T=Wwc`qTjL&q$>Ry2L zh_p`)rZ8iap}9>6uAb75MQQtYuqD&n(J?Y2Yu~138-mANMMECPC_{6bkXM&kmofmw z%w}^($H<6;wTbPgNVfIOURC!LTj&0%@8FT8?5# z8?&sV^_U5C)aqV1fOiutG~bEtnLM^*Ox$Vpc8p?$E=&RSemxm z0%=&?3%Wp$jb1nd&m9nJ9l0wIw!a@LC+F?zB}Ad z&3IDz=&T087j2;n^wuFOS34 Date: Sun, 27 Feb 2022 18:16:38 +0000 Subject: [PATCH 04/27] android: refactor modal views without navigation controller (#381) * android: refactor modal views without navigation controller * refactor navigation * make alert manager global * disable CRPendingSubSummary in terminal, hamburger menu instead of gear --- .../java/chat/simplex/app/MainActivity.kt | 129 ++++++------------ .../main/java/chat/simplex/app/SimplexApp.kt | 71 +--------- .../java/chat/simplex/app/model/ChatModel.kt | 3 +- .../java/chat/simplex/app/model/SimpleXAPI.kt | 19 +-- .../java/chat/simplex/app/views/SplashView.kt | 11 +- .../chat/simplex/app/views/TerminalView.kt | 58 ++++---- .../simplex/app/views/chat/ChatInfoView.kt | 13 +- .../chat/simplex/app/views/chat/ChatView.kt | 10 +- .../simplex/app/views/chat/SendMsgView.kt | 3 + .../app/views/chat/item/TextItemView.kt | 2 +- .../app/views/chatlist/ChatHelpView.kt | 107 ++++----------- .../app/views/chatlist/ChatListNavLinkView.kt | 12 +- .../app/views/chatlist/ChatListView.kt | 23 ++-- .../simplex/app/views/helpers/AlertManager.kt | 81 +++++++++++ .../simplex/app/views/helpers/ModalView.kt | 59 ++++++++ .../app/views/newchat/AddContactView.kt | 26 +--- .../app/views/newchat/ConnectContactView.kt | 100 ++++++-------- .../simplex/app/views/newchat/NewChatSheet.kt | 26 ++-- .../app/views/usersettings/HelpView.kt | 32 ++--- .../views/usersettings/MarkdownHelpView.kt | 88 +++++------- .../app/views/usersettings/SettingsView.kt | 33 +++-- .../app/views/usersettings/UserAddressView.kt | 17 +-- .../app/views/usersettings/UserProfileView.kt | 18 +-- apps/ios/SimpleX.xcodeproj/project.pbxproj | 61 +++++---- src/Simplex/Chat/View.hs | 5 +- 25 files changed, 447 insertions(+), 560 deletions(-) create mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt create mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 30ee947668..a41051877d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -8,22 +8,27 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.lifecycle.AndroidViewModel -import androidx.navigation.* -import androidx.navigation.compose.* -import chat.simplex.app.model.* +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.NtfManager import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.* -import chat.simplex.app.views.chat.ChatInfoView +import chat.simplex.app.views.SplashView +import chat.simplex.app.views.WelcomeView import chat.simplex.app.views.chat.ChatView import chat.simplex.app.views.chatlist.ChatListView import chat.simplex.app.views.chatlist.openChat +import chat.simplex.app.views.helpers.AlertManager import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.newchat.* -import chat.simplex.app.views.usersettings.* -import kotlinx.serialization.decodeFromString + +//import kotlinx.serialization.decodeFromString class MainActivity: ComponentActivity() { private val vm by viewModels() @@ -35,7 +40,13 @@ class MainActivity: ComponentActivity() { // vm.app.initiateBackgroundWork() setContent { SimpleXTheme { - Navigation(vm.chatModel) + Surface( + Modifier + .background(MaterialTheme.colors.background) + .fillMaxSize() + ) { + MainPage(vm.chatModel) + } } } } @@ -47,84 +58,20 @@ class SimplexViewModel(application: Application): AndroidViewModel(application) } @Composable -fun MainPage(chatModel: ChatModel, nav: NavController) { - when (chatModel.userCreated.value) { - null -> SplashView() - false -> WelcomeView(chatModel) // { nav.navigate(Pages.ChatList.route) } - true -> if (chatModel.chatId.value == null) { - ChatListView(chatModel, nav) - } else { - ChatView(chatModel, nav) - } - } -} - -@Composable -fun Navigation(chatModel: ChatModel) { - println("*** in Navigation") - val nav = rememberNavController() - val scope = rememberCoroutineScope() +fun MainPage(chatModel: ChatModel) { Box { - NavHost(navController = nav, startDestination = Pages.Home.route) { - composable(route = Pages.Home.route) { - println("*** composable MainPage") - MainPage(chatModel, nav) - } - composable(route = Pages.Welcome.route) { - WelcomeView(chatModel) - } - composable(route = Pages.AddContact.route) { - AddContactView(chatModel, nav) - } - composable(route = Pages.Connect.route) { - ConnectContactView(chatModel, nav) - } - composable(route = Pages.ChatInfo.route) { - ChatInfoView(chatModel, nav) - } - composable(route = Pages.Terminal.route) { - TerminalView(chatModel, nav) - } - composable( - Pages.TerminalItemDetails.route + "/{identifier}", - arguments = listOf( - navArgument("identifier") { - type = NavType.LongType - } - ) - ) { entry -> DetailView(entry.arguments!!.getLong("identifier"), chatModel.terminalItems, nav) } - composable(route = Pages.UserProfile.route) { - UserProfileView(chatModel, nav) - } - composable(route = Pages.UserAddress.route) { - UserAddressView(chatModel, nav) - } - composable(route = Pages.Help.route) { - HelpView(chatModel, nav) - } - composable(route = Pages.Markdown.route) { - MarkdownHelpView(nav) - } + when (chatModel.userCreated.value) { + null -> SplashView() + false -> WelcomeView(chatModel) + true -> + if (chatModel.chatId.value == null) ChatListView(chatModel) + else ChatView(chatModel) } - val am = chatModel.alertManager - if (am.presentAlert.value) am.alertView.value?.invoke() + ModalManager.shared.showInView() + AlertManager.shared.showInView() } } -sealed class Pages(val route: String) { - object Home: Pages("home") - object Terminal: Pages("terminal") - object Welcome: Pages("welcome") - object TerminalItemDetails: Pages("details") - object AddContact: Pages("add_contact") - object Connect: Pages("connect") - object ChatInfo: Pages("chat_info") - object UserProfile: Pages("user_profile") - object UserAddress: Pages("user_address") - object Help: Pages("help") - object Markdown: Pages("markdown") -} - fun processIntent(intent: Intent?, chatModel: ChatModel) { when (intent?.action) { NtfManager.OpenChatAction -> { @@ -148,8 +95,8 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { // TODO open from chat list view chatModel.appOpenUrl.value = uri } else { - withUriAction(chatModel, uri) { action -> - chatModel.alertManager.showAlertMsg( + withUriAction(uri) { action -> + AlertManager.shared.showAlertMsg( title = "Connect via $action link?", text = "Your profile will be sent to the contact that you received this link from.", confirmText = "Connect", @@ -164,10 +111,10 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { } } -fun testJson() { - val str = """ - {} - """.trimIndent() - - println(json.decodeFromString(str)) -} +//fun testJson() { +// val str = """ +// {} +// """.trimIndent() +// +// println(json.decodeFromString(str)) +//} diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index aa04839b3f..cb4f058c4b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -3,9 +3,6 @@ package chat.simplex.app import android.app.Application import android.net.LocalServerSocket import android.util.Log -import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf import androidx.work.* import chat.simplex.app.model.* import chat.simplex.app.views.helpers.withApi @@ -48,7 +45,7 @@ class SimplexApp: Application() { super.onCreate() ntfManager = NtfManager(applicationContext) val ctrl = chatInit(applicationContext.filesDir.toString()) - controller = ChatController(ctrl, AlertManager(), ntfManager, applicationContext) + controller = ChatController(ctrl, ntfManager, applicationContext) chatModel = controller.chatModel withApi { val user = controller.apiGetActiveUser() @@ -56,72 +53,6 @@ class SimplexApp: Application() { } } - class AlertManager { - var alertView = mutableStateOf<(@Composable () -> Unit)?>(null) - var presentAlert = mutableStateOf(false) - - fun showAlert(alert: @Composable () -> Unit) { - Log.d("SIMPLEX", "AlertManager.showAlert") - alertView.value = alert - presentAlert.value = true - } - - fun hideAlert() { - presentAlert.value = false - alertView.value = null - } - - fun showAlertDialog( - title: String, - text: String? = null, - confirmText: String = "Ok", - onConfirm: (() -> Unit)? = null, - dismissText: String = "Cancel", - onDismiss: (() -> Unit)? = null - ) { - val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } - showAlert { - AlertDialog( - onDismissRequest = this::hideAlert, - title = { Text(title) }, - text = alertText, - confirmButton = { - Button(onClick = { - onConfirm?.invoke() - hideAlert() - }) { Text(confirmText) } - }, - dismissButton = { - Button(onClick = { - onDismiss?.invoke() - hideAlert() - }) { Text(dismissText) } - } - ) - } - } - - fun showAlertMsg( - title: String, text: String? = null, - confirmText: String = "Ok", onConfirm: (() -> Unit)? = null - ) { - val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } - showAlert { - AlertDialog( - onDismissRequest = this::hideAlert, - title = { Text(title) }, - text = alertText, - confirmButton = { - Button(onClick = { - onConfirm?.invoke() - hideAlert() - }) { Text(confirmText) } - } - ) - } - } - } - companion object { init { val socketName = "local.socket.address.listen.native.cmd2" diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 726b2fb5d4..75b8c74220 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -7,13 +7,12 @@ 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.SimplexApp import chat.simplex.app.ui.theme.SecretColor import kotlinx.datetime.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -class ChatModel(val controller: ChatController, val alertManager: SimplexApp.AlertManager) { +class ChatModel(val controller: ChatController) { var currentUser = mutableStateOf(null) var userCreated = mutableStateOf(null) var chats = mutableStateListOf() diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 875b90b1e2..9e4b899f79 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -5,7 +5,9 @@ import android.app.ActivityManager.RunningAppProcessInfo import android.content.Context import android.util.Log import androidx.compose.runtime.mutableStateOf -import chat.simplex.app.* +import chat.simplex.app.chatRecvMsg +import chat.simplex.app.chatSendCmd +import chat.simplex.app.views.helpers.AlertManager import chat.simplex.app.views.helpers.withApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -18,8 +20,8 @@ import kotlin.concurrent.thread typealias ChatCtrl = Long -open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.AlertManager, val ntfManager: NtfManager, val appContext: Context) { - var chatModel = ChatModel(this, alertManager) +open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val appContext: Context) { + var chatModel = ChatModel(this) suspend fun startChat(u: User) { Log.d("SIMPLEX (user)", u.toString()) @@ -139,14 +141,14 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert when { r is CR.SentConfirmation || r is CR.SentInvitation -> return true r is CR.ContactAlreadyExists -> { - alertManager.showAlertMsg("Contact already exists", + AlertManager.shared.showAlertMsg("Contact already exists", "You are already connected to ${r.contact.displayName} via this link" ) return false } r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat && r.chatError.errorType is ChatErrorType.InvalidConnReq -> { - alertManager.showAlertMsg("Invalid connection link", + AlertManager.shared.showAlertMsg("Invalid connection link", "Please check that you used the correct link or ask your contact to send you another one." ) return false @@ -165,15 +167,14 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert is CR.ChatCmdError -> { val e = r.chatError if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) { - alertManager.showAlertMsg( + AlertManager.shared.showAlertMsg( "Can't delete contact!", "Contact ${e.errorType.contact.displayName} cannot be deleted, it is a member of the group(s) ${e.errorType.groupNames}" ) - return false } } + else -> apiErrorAlert("apiDeleteChat", "Error deleting ${type.chatTypeName}", r) } - apiErrorAlert("apiDeleteChat", "Error deleting ${type.chatTypeName}", r) return false } @@ -234,7 +235,7 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert fun apiErrorAlert(method: String, title: String, r: CR) { val errMsg = "${r.responseType}: ${r.details}" Log.e("SIMPLEX", "$method bad response: $errMsg") - alertManager.showAlertMsg(title, errMsg) + AlertManager.shared.showAlertMsg(title, errMsg) } fun processReceivedMsg(r: CR) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt index 4fa4b311d3..f37a6fca8e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt @@ -1,17 +1,18 @@ package chat.simplex.app.views import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color @Composable fun SplashView() { - Box(modifier = Modifier - .fillMaxSize() - .background(color = Color.White) + Surface( + Modifier + .background(MaterialTheme.colors.background) + .fillMaxSize() ) { // Image( // painter = painterResource(R.drawable.logo), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt index d12a4a62d9..5a9e3c0411 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -1,8 +1,10 @@ package chat.simplex.app.views import android.content.res.Configuration +import androidx.activity.compose.BackHandler import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.* import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* @@ -15,19 +17,20 @@ 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 androidx.navigation.NavController import chat.simplex.app.model.* import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.SendMsgView import chat.simplex.app.views.helpers.CloseSheetBar import chat.simplex.app.views.helpers.withApi +import chat.simplex.app.views.newchat.ModalManager import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsWithImePadding import kotlinx.coroutines.launch @Composable -fun TerminalView(chatModel: ChatModel, nav: NavController) { - TerminalLayout(chatModel.terminalItems, nav::popBackStack, nav::navigate) { cmd -> +fun TerminalView(chatModel: ChatModel, close: () -> Unit) { + BackHandler(onBack = close) + TerminalLayout(chatModel.terminalItems, close) { cmd -> withApi { // show "in progress" chatModel.controller.sendCmd(CC.Console(cmd)) @@ -37,39 +40,45 @@ fun TerminalView(chatModel: ChatModel, nav: NavController) { } @Composable -fun TerminalLayout(terminalItems: List , close: () -> Unit, navigate: (String) -> Unit, - sendCommand: (String) -> Unit) { +fun TerminalLayout(terminalItems: List , close: () -> Unit, sendCommand: (String) -> Unit) { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { Scaffold( topBar = { CloseSheetBar(close) }, bottomBar = { SendMsgView(sendCommand) }, modifier = Modifier.navigationBarsWithImePadding() ) { contentPadding -> - Box( + Surface( modifier = Modifier .padding(contentPadding) .fillMaxWidth() .background(MaterialTheme.colors.background) ) { - TerminalLog(terminalItems, navigate) + TerminalLog(terminalItems) } } } } @Composable -fun TerminalLog(terminalItems: List, navigate: (String) -> Unit) { +fun TerminalLog(terminalItems: List) { val listState = rememberLazyListState() val scope = rememberCoroutineScope() LazyColumn(state = listState) { items(terminalItems) { item -> - Text("${item.date.toString().subSequence(11, 19)} ${item.label}", - style = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 18.sp, color = MaterialTheme.colors.primary), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .padding(horizontal = 8.dp, vertical = 4.dp) - .clickable { navigate("details/${item.id}") }) + Text("${item.date.toString().subSequence(11, 19)} ${item.label}", + style = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 18.sp, color = MaterialTheme.colors.primary), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 4.dp) + .clickable { + ModalManager.shared.showModal { + SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) { + Text(item.details) + } + } + } + ) } val len = terminalItems.count() if (len > 1) { @@ -80,22 +89,6 @@ fun TerminalLog(terminalItems: List, navigate: (String) -> Unit) { } } -@Composable -fun DetailView(identifier: Long, terminalItems: List, nav: NavController){ - Surface( - Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize() - ) { - Column { - CloseSheetBar(nav::popBackStack) - SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) { - Text((terminalItems.firstOrNull { it.id == identifier })?.details ?: "") - } - } - } -} - @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -108,7 +101,6 @@ fun PreviewTerminalLayout() { TerminalLayout( terminalItems = TerminalItem.sampleData, close = {}, - navigate = {}, sendCommand = {} ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index 061fab5e3b..cc272b8fb8 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -1,5 +1,6 @@ package chat.simplex.app.views.chat +import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -14,19 +15,19 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* @Composable -fun ChatInfoView(chatModel: ChatModel, nav: NavController) { +fun ChatInfoView(chatModel: ChatModel, close: () -> Unit) { + BackHandler(onBack = close) val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value } if (chat != null) { ChatInfoLayout(chat, - close = nav::popBackStack, + close = close, deleteContact = { - chatModel.alertManager.showAlertMsg( + AlertManager.shared.showAlertMsg( title = "Delete contact?", text = "Contact and all messages will be deleted - this cannot be undone!", confirmText = "Delete", @@ -37,7 +38,7 @@ fun ChatInfoView(chatModel: ChatModel, nav: NavController) { if (r) { chatModel.removeChat(cInfo.id) chatModel.chatId.value = null - nav.popBackStack() + close() } } } @@ -92,7 +93,7 @@ fun ChatInfoLayout(chat: Chat, close: () -> Unit, deleteContact: () -> Unit) { Spacer(Modifier.weight(1F)) - Box(Modifier.padding(24.dp)) { + Box(Modifier.padding(48.dp)) { SimpleButton( "Delete contact", icon = Icons.Outlined.Delete, color = Color.Red, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 7270720ebf..774afd42ef 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -18,13 +18,12 @@ 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 androidx.navigation.NavController -import chat.simplex.app.Pages import chat.simplex.app.model.* import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.item.ChatItemView import chat.simplex.app.views.helpers.ChatInfoImage import chat.simplex.app.views.helpers.withApi +import chat.simplex.app.views.newchat.ModalManager import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsWithImePadding import kotlinx.coroutines.delay @@ -32,7 +31,7 @@ import kotlinx.coroutines.launch import kotlinx.datetime.Clock @Composable -fun ChatView(chatModel: ChatModel, nav: NavController) { +fun ChatView(chatModel: ChatModel) { val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } if (chat == null) { chatModel.chatId.value = null @@ -55,7 +54,7 @@ fun ChatView(chatModel: ChatModel, nav: NavController) { } ChatLayout(chat, chatModel.chatItems, back = { chatModel.chatId.value = null }, - info = { nav.navigate(Pages.ChatInfo.route) }, + info = { ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, close) } }, sendMessage = { msg -> withApi { // show "in progress" @@ -86,9 +85,8 @@ fun ChatLayout( bottomBar = { SendMsgView(sendMessage) }, modifier = Modifier.navigationBarsWithImePadding() ) { contentPadding -> - Box( + Surface( modifier = Modifier - .padding(contentPadding) .fillMaxWidth() .background(MaterialTheme.colors.background) ) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index d9df249303..639933dde5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -15,9 +15,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.SimpleXTheme @Composable @@ -33,6 +35,7 @@ fun SendMsgView(sendMessage: (String) -> Unit) { autoCorrect = true ), modifier = Modifier.padding(8.dp), + cursorBrush = SolidColor(HighOrLowlight), decorationBox = { innerTextField -> Surface( shape = RoundedCornerShape(18.dp), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt index 949648138e..df2528dd25 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt @@ -58,7 +58,7 @@ fun appendGroupMember(b: AnnotatedString.Builder, chatItem: ChatItem, groupMembe @Composable fun MarkdownText ( chatItem: ChatItem, - style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground), + style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onSurface), maxLines: Int = Int.MAX_VALUE, overflow: TextOverflow = TextOverflow.Clip, uriHandler: UriHandler? = null, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt index 8fb912c6d5..c12239f580 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt @@ -1,7 +1,6 @@ package chat.simplex.app.views.chat import android.content.res.Configuration -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -18,35 +17,28 @@ import androidx.compose.ui.unit.dp import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.usersettings.simplexTeamUri +val bold = SpanStyle(fontWeight = FontWeight.Bold) + @Composable -fun ChatHelpView(addContact: () -> Unit, doAddContact: Boolean) { +fun ChatHelpView(addContact: (() -> Unit)? = null) { Column( - Modifier - .fillMaxWidth() - .background(MaterialTheme.colors.background), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(10.dp) ) { val uriHandler = LocalUriHandler.current - Text( - "Thank you for installing SimpleX Chat!", - color = MaterialTheme.colors.onBackground - ) + Text("Thank you for installing SimpleX Chat!") Text( buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append("You can ") - } + append("You can ") withStyle(SpanStyle(color = MaterialTheme.colors.primary)) { append("connect to SimpleX team") } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append(".") - } + append(".") }, - modifier = Modifier - .clickable(onClick = { uriHandler.openUri(simplexTeamUri) }) + modifier = Modifier.clickable(onClick = { + uriHandler.openUri(simplexTeamUri) + }) ) Column( @@ -56,46 +48,30 @@ fun ChatHelpView(addContact: () -> Unit, doAddContact: Boolean) { ) { Text( "To start a new chat", - color = MaterialTheme.colors.onBackground, style = MaterialTheme.typography.h2 ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - Text( - "Tap button", - color = MaterialTheme.colors.onBackground - ) + Text("Tap button") Icon( Icons.Outlined.PersonAdd, "Add Contact", - modifier = if (doAddContact) Modifier.clickable(onClick = addContact) else Modifier, - tint = MaterialTheme.colors.onBackground, - ) - Text( - "above, then:", - color = MaterialTheme.colors.onBackground + modifier = if (addContact != null) Modifier.clickable(onClick = addContact) else Modifier, ) + Text("above, then:") } Text( buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) { - append("Add new contact") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append(": to create your one-time QR Code for your contact.") - } + withStyle(bold) { append("Add new contact") } + append(": to create your one-time QR Code for your contact.") } ) Text( buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) { - append("Scan QR code") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append(": to connect to your contact who shows QR code to you.") - } + withStyle(bold) { append("Scan QR code") } + append(": to connect to your contact who shows QR code to you.") } ) } @@ -105,51 +81,22 @@ fun ChatHelpView(addContact: () -> Unit, doAddContact: Boolean) { horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(10.dp) ) { - Text( - "To connect via link", - color = MaterialTheme.colors.onBackground, - style = MaterialTheme.typography.h2 - ) - Text( - "If you received SimpleX Chat invitation link you can open it in your browser:", - color = MaterialTheme.colors.onBackground - ) + Text("To connect via link", style = MaterialTheme.typography.h2) + Text("If you received SimpleX Chat invitation link you can open it in your browser:") Text( buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append("\uD83D\uDCBB desktop: scan displayed QR code from the app, via ") - } - withStyle( - SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold) - ) { - append("Scan QR code") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append(".") - } + append("\uD83D\uDCBB desktop: scan displayed QR code from the app, via ") + withStyle(bold) { append("Scan QR code") } + append(".") } ) Text( buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append("\uD83D\uDCF1 mobile: tap ") - } - withStyle( - SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold) - ) { - append("Open in mobile app") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append(", then tap ") - } - withStyle( - SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold) - ) { - append("Connect") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append(" in the app.") - } + append("\uD83D\uDCF1 mobile: tap ") + withStyle(bold) { append("Open in mobile app") } + append(", then tap ") + withStyle(bold) { append("Connect") } + append(" in the app.") } ) } @@ -165,6 +112,6 @@ fun ChatHelpView(addContact: () -> Unit, doAddContact: Boolean) { @Composable fun PreviewChatHelpLayout() { SimpleXTheme { - ChatHelpView({}, false) + ChatHelpView({}) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt index 9efbb1183a..fa25259b37 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt @@ -11,20 +11,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import chat.simplex.app.Pages + import chat.simplex.app.model.* import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.helpers.AlertManager import chat.simplex.app.views.helpers.withApi import kotlinx.datetime.Clock @Composable -fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel, nav: NavController) { +fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { ChatListNavLinkLayout( chat = chat, click = { if (chat.chatInfo is ChatInfo.ContactRequest) { - contactRequestAlertDialog(chat.chatInfo, chatModel, nav) + contactRequestAlertDialog(chat.chatInfo, chatModel) } else { withApi { openChat(chatModel, chat.chatInfo) } } @@ -40,8 +40,8 @@ suspend fun openChat(chatModel: ChatModel, cInfo: ChatInfo) { } } -fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel, navController: NavController) { - chatModel.alertManager.showAlertDialog( +fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { + AlertManager.shared.showAlertDialog( title = "Accept connection request?", text = "If you choose to reject sender will NOT be notified", confirmText = "Accept", diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index 6f06d3e366..65b216fdee 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -8,15 +8,13 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.PersonAdd -import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import chat.simplex.app.model.ChatModel import chat.simplex.app.views.chat.ChatHelpView import chat.simplex.app.views.newchat.NewChatSheet @@ -39,7 +37,7 @@ class ScaffoldController(val scope: CoroutineScope) { } fun toggleSheet() { - if (state.bottomSheetState.isExpanded ?: false) collapse() else expand() + if (state.bottomSheetState.isExpanded) collapse() else expand() } fun toggleDrawer() = scope.launch { @@ -62,25 +60,24 @@ fun scaffoldController(): ScaffoldController { } @Composable -fun ChatListView(chatModel: ChatModel, nav: NavController) { +fun ChatListView(chatModel: ChatModel) { val scaffoldCtrl = scaffoldController() BottomSheetScaffold( scaffoldState = scaffoldCtrl.state, - drawerContent = { SettingsView(chatModel, nav) }, + drawerContent = { SettingsView(chatModel) }, sheetPeekHeight = 0.dp, - sheetContent = { NewChatSheet(chatModel, scaffoldCtrl, nav) }, + sheetContent = { NewChatSheet(chatModel, scaffoldCtrl) }, sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp), ) { Box { Column( modifier = Modifier - .padding(vertical = 8.dp) .fillMaxSize() .background(MaterialTheme.colors.background) ) { ChatListToolbar(scaffoldCtrl) if (chatModel.chats.isNotEmpty()) { - ChatList(chatModel, nav) + ChatList(chatModel) } else { val user = chatModel.currentUser.value Help(scaffoldCtrl, displayName = user?.profile?.displayName) @@ -111,7 +108,7 @@ fun Help(scaffoldCtrl: ScaffoldController, displayName: String?) { style = MaterialTheme.typography.h1, color = MaterialTheme.colors.onBackground ) - ChatHelpView({ scaffoldCtrl.toggleSheet() }, true) + ChatHelpView({ scaffoldCtrl.toggleSheet() }) Row( Modifier.padding(top = 30.dp), verticalAlignment = Alignment.CenterVertically, @@ -143,7 +140,7 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) { ) { IconButton(onClick = { scaffoldCtrl.toggleDrawer() }) { Icon( - Icons.Outlined.Settings, + Icons.Outlined.Menu, "Settings", tint = MaterialTheme.colors.primary, modifier = Modifier.padding(10.dp) @@ -167,13 +164,13 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) { } @Composable -fun ChatList(chatModel: ChatModel, navController: NavController) { +fun ChatList(chatModel: ChatModel) { Divider(Modifier.padding(horizontal = 8.dp)) LazyColumn( modifier = Modifier.fillMaxWidth() ) { items(chatModel.chats) { chat -> - ChatListNavLinkView(chat, chatModel, navController) + ChatListNavLinkView(chat, chatModel) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt new file mode 100644 index 0000000000..610ffc949b --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt @@ -0,0 +1,81 @@ +package chat.simplex.app.views.helpers + +import android.util.Log +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf + +class AlertManager { + var alertView = mutableStateOf<(@Composable () -> Unit)?>(null) + var presentAlert = mutableStateOf(false) + + fun showAlert(alert: @Composable () -> Unit) { + Log.d("SIMPLEX", "AlertManager.showAlert") + alertView.value = alert + presentAlert.value = true + } + + fun hideAlert() { + presentAlert.value = false + alertView.value = null + } + + fun showAlertDialog( + title: String, + text: String? = null, + confirmText: String = "Ok", + onConfirm: (() -> Unit)? = null, + dismissText: String = "Cancel", + onDismiss: (() -> Unit)? = null + ) { + val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } + showAlert { + AlertDialog( + onDismissRequest = this::hideAlert, + title = { Text(title) }, + text = alertText, + confirmButton = { + Button(onClick = { + onConfirm?.invoke() + hideAlert() + }) { Text(confirmText) } + }, + dismissButton = { + Button(onClick = { + onDismiss?.invoke() + hideAlert() + }) { Text(dismissText) } + } + ) + } + } + + fun showAlertMsg( + title: String, text: String? = null, + confirmText: String = "Ok", onConfirm: (() -> Unit)? = null + ) { + val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } + showAlert { + AlertDialog( + onDismissRequest = this::hideAlert, + title = { Text(title) }, + text = alertText, + confirmButton = { + Button(onClick = { + onConfirm?.invoke() + hideAlert() + }) { Text(confirmText) } + } + ) + } + } + + @Composable + fun showInView() { + if (presentAlert.value) alertView.value?.invoke() + } + + companion object { + val shared = AlertManager() + } +} \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt new file mode 100644 index 0000000000..e0401c7099 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt @@ -0,0 +1,59 @@ +package chat.simplex.app.views.newchat + +import android.util.Log +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import chat.simplex.app.views.helpers.CloseSheetBar + +@Composable +fun ModalView(close: () -> Unit, content: @Composable () -> Unit) { + BackHandler(onBack = close) + Surface( + Modifier + .background(MaterialTheme.colors.background) + .fillMaxSize() + ) { + Column { + CloseSheetBar(close) + Box(Modifier.padding(horizontal = 16.dp)) { content() } + } + } +} + +class ModalManager { + private val modalViews = arrayListOf<(@Composable (close: () -> Unit) -> Unit)?>() + private val modalCount = mutableStateOf(0) + + fun showModal(content: @Composable () -> Unit) { + showCustomModal { close -> ModalView(close, content) } + } + + fun showCustomModal(modal: @Composable (close: () -> Unit) -> Unit) { + Log.d("SIMPLEX", "ModalManager.showModal") + modalViews.add(modal) + modalCount.value = modalViews.count() + } + + fun closeModal() { + if (!modalViews.isEmpty()) { + modalViews.removeAt(modalViews.count() - 1) + } + modalCount.value = modalViews.count() + } + + @Composable + fun showInView() { + if (modalCount.value > 0) modalViews.lastOrNull()?.invoke(::closeModal) + } + + companion object { + val shared = ModalManager() + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt index f221c97567..be27d1d6e0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt @@ -1,7 +1,6 @@ package chat.simplex.app.views.newchat import android.content.res.Configuration -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -16,60 +15,46 @@ 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.navigation.NavController import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.SimpleButton import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.CloseSheetBar import chat.simplex.app.views.helpers.shareText @Composable -fun AddContactView(chatModel: ChatModel, nav: NavController) { +fun AddContactView(chatModel: ChatModel) { val connReq = chatModel.connReqInvitation if (connReq != null) { val cxt = LocalContext.current AddContactLayout( connReq = connReq, - close = { nav.popBackStack() }, share = { shareText(cxt, connReq) } ) } } @Composable -fun AddContactLayout(connReq: String, close: () -> Unit, share: () -> Unit) { +fun AddContactLayout(connReq: String, share: () -> Unit) { Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) - .padding(horizontal = 8.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp) ) { - CloseSheetBar(close) Text( "Add contact", style = MaterialTheme.typography.h1, - color = MaterialTheme.colors.onBackground ) Text( "Show QR code to your contact\nto scan from the app", style = MaterialTheme.typography.h2, textAlign = TextAlign.Center, - color = MaterialTheme.colors.onBackground ) QRCode(connReq) Text( buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append("If you cannot meet in person, you can ") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) { + append("If you cannot meet in person, you can ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append("scan QR code in the video call") } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { - append(", or you can share the invitation link via any other channel.") - } + append(", or you can share the invitation link via any other channel.") }, textAlign = TextAlign.Center, style = MaterialTheme.typography.caption, @@ -92,7 +77,6 @@ fun PreviewAddContactView() { SimpleXTheme { AddContactLayout( connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", - close = {}, share = {} ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt index e9715229f0..4abcf2bc46 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt @@ -2,7 +2,7 @@ package chat.simplex.app.views.newchat import android.content.res.Configuration import android.net.Uri -import androidx.compose.foundation.background +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable @@ -13,44 +13,41 @@ 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.navigation.NavController import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.CloseSheetBar +import chat.simplex.app.views.helpers.AlertManager import chat.simplex.app.views.helpers.withApi @Composable -fun ConnectContactView(chatModel: ChatModel, nav: NavController) { +fun ConnectContactView(chatModel: ChatModel, close: () -> Unit) { + BackHandler(onBack = close) ConnectContactLayout( qrCodeScanner = { QRCodeScanner { connReqUri -> try { val uri = Uri.parse(connReqUri) - withUriAction(chatModel, uri) { action -> + withUriAction(uri) { action -> connectViaUri(chatModel, action, uri) } } catch (e: RuntimeException) { - chatModel.alertManager.showAlertMsg( + AlertManager.shared.showAlertMsg( title = "Invalid QR code", text = "This QR code is not a link!" ) } - nav.popBackStack() + close() } }, - close = { nav.popBackStack() } + close = close ) } -fun withUriAction( - chatModel: ChatModel, uri: Uri, - run: suspend (String) -> Unit -) { +fun withUriAction(uri: Uri, run: suspend (String) -> Unit) { val action = uri.path?.drop(1) if (action == "contact" || action == "invitation") { withApi { run(action) } } else { - chatModel.alertManager.showAlertMsg( + AlertManager.shared.showAlertMsg( title = "Invalid link!", text = "This link is not a valid connection link!" ) @@ -63,7 +60,7 @@ suspend fun connectViaUri(chatModel: ChatModel, action: String, uri: Uri) { val whenConnected = if (action == "contact") "your connection request is accepted" else "your contact's device is online" - chatModel.alertManager.showAlertMsg( + AlertManager.shared.showAlertMsg( title = "Connection request sent!", text = "You will be connected when $whenConnected, please wait or check later!" ) @@ -72,50 +69,41 @@ suspend fun connectViaUri(chatModel: ChatModel, action: String, uri: Uri) { @Composable fun ConnectContactLayout(qrCodeScanner: @Composable () -> Unit, close: () -> Unit) { - Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) - .padding(horizontal = 8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - CloseSheetBar(close) - Text( - "Scan QR code", - style = MaterialTheme.typography.h1, - color = MaterialTheme.colors.onBackground - ) - Text( - "Your chat profile will be sent\nto your contact", - style = MaterialTheme.typography.h2, - textAlign = TextAlign.Center, - color = MaterialTheme.colors.onBackground, - modifier = Modifier.padding(bottom = 4.dp) - ) - Box( - Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1F) - ) { qrCodeScanner() } - Text( - buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { + ModalView(close) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + "Scan QR code", + style = MaterialTheme.typography.h1, + ) + Text( + "Your chat profile will be sent\nto your contact", + style = MaterialTheme.typography.h2, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 4.dp) + ) + Box( + Modifier + .fillMaxWidth() + .aspectRatio(ratio = 1F) + ) { qrCodeScanner() } + Text( + buildAnnotatedString { append("If you cannot meet in person, you can ") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) { - append("scan QR code in the video call") - } - withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append("scan QR code in the video call") + } append(", or you can create the invitation link.") - } - }, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.caption, - modifier = Modifier - .padding(horizontal = 16.dp) - .padding(top = 4.dp) - ) + }, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.caption, + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 4.dp) + ) + } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt index 4b338f4f3d..65d882ae71 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt @@ -14,8 +14,6 @@ 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.navigation.NavController -import chat.simplex.app.Pages import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.SimpleXTheme @@ -24,7 +22,7 @@ import chat.simplex.app.views.helpers.withApi import com.google.accompanist.permissions.rememberPermissionState @Composable -fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController, nav: NavController) { +fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) NewChatSheetLayout( addContact = { @@ -34,13 +32,13 @@ fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController, nav: Nav // hide spinner if (chatModel.connReqInvitation != null) { newChatCtrl.collapse() - nav.navigate(Pages.AddContact.route) + ModalManager.shared.showModal { AddContactView(chatModel) } } } }, scanCode = { newChatCtrl.collapse() - nav.navigate(Pages.Connect.route) + ModalManager.shared.showCustomModal { close -> ConnectContactView(chatModel, close) } cameraPermissionState.launchPermissionRequest() } ) @@ -48,24 +46,34 @@ fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController, nav: Nav @Composable fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) { - Row(Modifier + Row( + Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 48.dp), horizontalArrangement = Arrangement.SpaceEvenly ) { - Box(Modifier.weight(1F).fillMaxWidth()) { + Box( + Modifier + .weight(1F) + .fillMaxWidth()) { ActionButton( "Add contact", "(create QR code\nor link)", Icons.Outlined.PersonAdd, click = addContact ) } - Box(Modifier.weight(1F).fillMaxWidth()) { + Box( + Modifier + .weight(1F) + .fillMaxWidth()) { ActionButton( "Scan QR code", "(in person or in video call)", Icons.Outlined.QrCode, click = scanCode ) } - Box(Modifier.weight(1F).fillMaxWidth()) { + Box( + Modifier + .weight(1F) + .fillMaxWidth()) { ActionButton( "Create Group", "(coming soon!)", Icons.Outlined.GroupAdd, disabled = true diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HelpView.kt index fcc225912e..9d7d380c7a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HelpView.kt @@ -1,8 +1,8 @@ package chat.simplex.app.views.usersettings import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -10,40 +10,27 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.ChatHelpView -import chat.simplex.app.views.helpers.CloseSheetBar @Composable -fun HelpView(chatModel: ChatModel, nav: NavController) { +fun HelpView(chatModel: ChatModel) { val user = chatModel.currentUser.value if (user != null) { - HelpLayout( - displayName = user.profile.displayName, - back = nav::popBackStack - ) + HelpLayout(displayName = user.profile.displayName) } } @Composable -fun HelpLayout(displayName: String, back: () -> Unit) { - Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) - .padding(horizontal = 8.dp), - horizontalAlignment = Alignment.Start - ) { - CloseSheetBar(back) +fun HelpLayout(displayName: String) { + Column(horizontalAlignment = Alignment.Start) { Text( "Welcome $displayName!", Modifier.padding(bottom = 24.dp), style = MaterialTheme.typography.h1, - color = MaterialTheme.colors.onBackground ) - ChatHelpView({}, false) + ChatHelpView() } } @@ -56,9 +43,6 @@ fun HelpLayout(displayName: String, back: () -> Unit) { @Composable fun PreviewHelpView() { SimpleXTheme { - HelpLayout( - displayName = "Alice", - back = {} - ) + HelpLayout(displayName = "Alice") } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt index 0e9ec8fb87..40def46dba 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt @@ -1,69 +1,53 @@ package chat.simplex.app.views.usersettings import android.content.res.Configuration -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.* import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import chat.simplex.app.model.Format import chat.simplex.app.model.FormatColor import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.CloseSheetBar @Composable -fun MarkdownHelpView(nav: NavController) { - MarkdownHelpLayout(nav::popBackStack) -} - -@Composable -fun MarkdownHelpLayout(back: () -> Unit) { - Surface( - Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize() - ) { - Column { - CloseSheetBar(back) - Column(Modifier.padding(horizontal = 16.dp)) { - Text( - "How to use markdown", - style = MaterialTheme.typography.h1, - ) - Text( - "You can use markdown to format messages:", - Modifier.padding(vertical = 16.dp) - ) - MdFormat("*bold*", "bold text", Format.Bold()) - MdFormat("_italic_", "italic text", Format.Italic()) - MdFormat("~strike~", "strikethrough text", Format.StrikeThrough()) - MdFormat("`code`", "a = b + c", Format.Snippet()) - Row { - MdSyntax("!1 colored!") - Text(buildAnnotatedString { - withStyle(Format.Colored(FormatColor.red).style) { append("red text") } - append(" (") - appendColor(this, "1", FormatColor.red, ", ") - appendColor(this, "2", FormatColor.green, ", ") - appendColor(this, "3", FormatColor.blue, ", ") - appendColor(this, "4", FormatColor.yellow, ", ") - appendColor(this, "5", FormatColor.cyan, ", ") - appendColor(this, "6", FormatColor.magenta, ")") - }) - } - Row { - MdSyntax("#secret#") - SelectionContainer { - Text(buildAnnotatedString { - withStyle(Format.Secret().style) { append("secret text") } - }) - } - } +fun MarkdownHelpView() { + Column(Modifier.padding(horizontal = 16.dp)) { + Text( + "How to use markdown", + style = MaterialTheme.typography.h1, + ) + Text( + "You can use markdown to format messages:", + Modifier.padding(vertical = 16.dp) + ) + MdFormat("*bold*", "bold text", Format.Bold()) + MdFormat("_italic_", "italic text", Format.Italic()) + MdFormat("~strike~", "strikethrough text", Format.StrikeThrough()) + MdFormat("`code`", "a = b + c", Format.Snippet()) + Row { + MdSyntax("!1 colored!") + Text(buildAnnotatedString { + withStyle(Format.Colored(FormatColor.red).style) { append("red text") } + append(" (") + appendColor(this, "1", FormatColor.red, ", ") + appendColor(this, "2", FormatColor.green, ", ") + appendColor(this, "3", FormatColor.blue, ", ") + appendColor(this, "4", FormatColor.yellow, ", ") + appendColor(this, "5", FormatColor.cyan, ", ") + appendColor(this, "6", FormatColor.magenta, ")") + }) + } + Row { + MdSyntax("#secret#") + SelectionContainer { + Text(buildAnnotatedString { + withStyle(Format.Secret().style) { append("secret text") } + }) } } } @@ -102,6 +86,6 @@ fun appendColor(b: AnnotatedString.Builder, s: String, c: FormatColor, after: St @Composable fun PreviewMarkdownHelpView() { SimpleXTheme { - MarkdownHelpLayout(back = {}) + MarkdownHelpView() } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index 9581ca62bc..0e55619137 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -17,20 +17,24 @@ 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.dp -import androidx.navigation.NavController -import chat.simplex.app.Pages import chat.simplex.app.R import chat.simplex.app.model.ChatModel import chat.simplex.app.model.Profile import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.TerminalView +import chat.simplex.app.views.newchat.ModalManager @Composable -fun SettingsView(chatModel: ChatModel, nav: NavController) { +fun SettingsView(chatModel: ChatModel) { val user = chatModel.currentUser.value if (user != null) { SettingsLayout( profile = user.profile, - navigate = nav::navigate +// showModal = { modal -> ModalManager.shared.showModal { modal(chatModel) } }, + showProfile = { ModalManager.shared.showModal { UserProfileView(chatModel) } }, + showAddress = { ModalManager.shared.showModal { UserAddressView(chatModel) } }, + showHelp = { ModalManager.shared.showModal { HelpView(chatModel) } }, + showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } } ) } } @@ -41,7 +45,11 @@ val simplexTeamUri = @Composable fun SettingsLayout( profile: Profile, - navigate: (String) -> Unit +// showModal: (modal: @Composable (ChatModel) -> Unit) -> Unit, + showProfile: () -> Unit, + showAddress: () -> Unit, + showHelp: () -> Unit, + showTerminal: () -> Unit ) { val uriHandler = LocalUriHandler.current Surface( @@ -62,7 +70,7 @@ fun SettingsLayout( ) Spacer(Modifier.height(30.dp)) - SettingsSectionView({ navigate(Pages.UserProfile.route) }, 60.dp) { + SettingsSectionView(showProfile, 60.dp) { Icon( Icons.Outlined.AccountCircle, contentDescription = "Avatar Placeholder", @@ -78,7 +86,7 @@ fun SettingsLayout( } } Divider(Modifier.padding(horizontal = 8.dp)) - SettingsSectionView({ navigate(Pages.UserAddress.route) }) { + SettingsSectionView(showAddress) { Icon( Icons.Outlined.QrCode, contentDescription = "Address", @@ -88,7 +96,7 @@ fun SettingsLayout( } Spacer(Modifier.height(24.dp)) - SettingsSectionView({ navigate(Pages.Help.route) }) { + SettingsSectionView(showHelp) { Icon( Icons.Outlined.HelpOutline, contentDescription = "Chat help", @@ -96,7 +104,7 @@ fun SettingsLayout( Spacer(Modifier.padding(horizontal = 4.dp)) Text("How to use SimpleX Chat") } - SettingsSectionView({ navigate(Pages.Markdown.route) }) { + SettingsSectionView({ ModalManager.shared.showModal { MarkdownHelpView() } }) { Icon( Icons.Outlined.TextFormat, contentDescription = "Markdown help", @@ -130,7 +138,7 @@ fun SettingsLayout( } Spacer(Modifier.height(24.dp)) - SettingsSectionView({ navigate(Pages.Terminal.route) }) { + SettingsSectionView(showTerminal) { Icon( painter = painterResource(id = R.drawable.ic_outline_terminal), contentDescription = "Chat console", @@ -183,7 +191,10 @@ fun PreviewSettingsLayout() { SimpleXTheme { SettingsLayout( profile = Profile.sampleData, - navigate = {} + showProfile = {}, + showAddress = {}, + showHelp = {}, + showTerminal = {} ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt index d082048f8d..a6a36bf16f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt @@ -1,7 +1,6 @@ package chat.simplex.app.views.usersettings import android.content.res.Configuration -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -14,7 +13,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.SimpleButton import chat.simplex.app.ui.theme.SimpleXTheme @@ -22,11 +20,10 @@ import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.QRCode @Composable -fun UserAddressView(chatModel: ChatModel, nav: NavController) { +fun UserAddressView(chatModel: ChatModel) { val cxt = LocalContext.current UserAddressLayout( userAddress = chatModel.userAddress.value, - back = { nav.popBackStack() }, createAddress = { withApi { chatModel.userAddress.value = chatModel.controller.apiCreateUserAddress() @@ -34,7 +31,7 @@ fun UserAddressView(chatModel: ChatModel, nav: NavController) { }, share = { userAddress: String -> shareText(cxt, userAddress) }, deleteAddress = { - chatModel.alertManager.showAlertMsg( + AlertManager.shared.showAlertMsg( title = "Delete address?", text = "All your contacts will remain connected", confirmText = "Delete", @@ -52,31 +49,23 @@ fun UserAddressView(chatModel: ChatModel, nav: NavController) { @Composable fun UserAddressLayout( userAddress: String?, - back: () -> Unit, createAddress: () -> Unit, share: (String) -> Unit, deleteAddress: () -> Unit ) { Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) - .padding(horizontal = 8.dp), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top ) { - CloseSheetBar(back) Text( "Your chat address", Modifier.padding(bottom = 24.dp), style = MaterialTheme.typography.h1, - color = MaterialTheme.colors.onBackground ) Text( "You can share your address as a link or as a QR code - anybody will be able to connect to you, " + "and if you later delete it - you won't lose your contacts.", Modifier.padding(bottom = 24.dp), - color = MaterialTheme.colors.onBackground ) Column( Modifier @@ -119,7 +108,6 @@ fun PreviewUserAddressLayoutNoAddress() { SimpleXTheme { UserAddressLayout( userAddress = null, - back = {}, createAddress = {}, share = { _ -> }, deleteAddress = {}, @@ -138,7 +126,6 @@ fun PreviewUserAddressLayoutAddressCreated() { SimpleXTheme { UserAddressLayout( userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", - back = {}, createAddress = {}, share = { _ -> }, deleteAddress = {}, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt index 6af1f44db1..4dd2ad0d44 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt @@ -1,7 +1,6 @@ package chat.simplex.app.views.usersettings import android.content.res.Configuration -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.BasicTextField @@ -15,15 +14,13 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import chat.simplex.app.model.ChatModel import chat.simplex.app.model.Profile import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.CloseSheetBar import chat.simplex.app.views.helpers.withApi @Composable -fun UserProfileView(chatModel: ChatModel, nav: NavController) { +fun UserProfileView(chatModel: ChatModel) { val user = chatModel.currentUser.value if (user != null) { var editProfile by remember { mutableStateOf(false) } @@ -31,7 +28,6 @@ fun UserProfileView(chatModel: ChatModel, nav: NavController) { UserProfileLayout( editProfile = editProfile, profile = profile, - back = { nav.popBackStack() }, editProfileOff = { editProfile = false }, editProfileOn = { editProfile = true }, saveProfile = { displayName: String, fullName: String -> @@ -54,19 +50,11 @@ fun UserProfileView(chatModel: ChatModel, nav: NavController) { fun UserProfileLayout( editProfile: Boolean, profile: Profile, - back: () -> Unit, editProfileOff: () -> Unit, editProfileOn: () -> Unit, saveProfile: (String, String) -> Unit, ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) - .padding(horizontal = 8.dp), - horizontalAlignment = Alignment.Start - ) { - CloseSheetBar(back) + Column(horizontalAlignment = Alignment.Start) { Text( "Your chat profile", Modifier.padding(bottom = 24.dp), @@ -185,7 +173,6 @@ fun PreviewUserProfileLayoutEditOff() { UserProfileLayout( profile = Profile.sampleData, editProfile = false, - back = {}, editProfileOff = {}, editProfileOn = {}, saveProfile = { _, _ -> } @@ -205,7 +192,6 @@ fun PreviewUserProfileLayoutEditOn() { UserProfileLayout( profile = Profile.sampleData, editProfile = true, - back = {}, editProfileOff = {}, editProfileOn = {}, saveProfile = { _, _ -> } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 617f9d6479..09221d89a5 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -13,16 +13,6 @@ 5C116CDD27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; 5C1A4C1F27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; - 5C27D00827C7D8B500DD6182 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00327C7D8B500DD6182 /* libgmpxx.a */; }; - 5C27D00927C7D8B500DD6182 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00327C7D8B500DD6182 /* libgmpxx.a */; }; - 5C27D00A27C7D8B500DD6182 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00427C7D8B500DD6182 /* libgmp.a */; }; - 5C27D00B27C7D8B500DD6182 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00427C7D8B500DD6182 /* libgmp.a */; }; - 5C27D00C27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */; }; - 5C27D00D27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */; }; - 5C27D00E27C7D8B500DD6182 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00627C7D8B500DD6182 /* libffi.a */; }; - 5C27D00F27C7D8B500DD6182 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00627C7D8B500DD6182 /* libffi.a */; }; - 5C27D01027C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */; }; - 5C27D01127C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */; }; 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; }; 5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; }; 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; }; @@ -39,6 +29,16 @@ 5C5346A927B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; }; 5C577F7E27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; }; + 5C67D2FA27CB9FA100E4261F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F527CB9FA100E4261F /* libgmp.a */; }; + 5C67D2FB27CB9FA100E4261F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F527CB9FA100E4261F /* libgmp.a */; }; + 5C67D2FC27CB9FA100E4261F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F627CB9FA100E4261F /* libgmpxx.a */; }; + 5C67D2FD27CB9FA100E4261F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F627CB9FA100E4261F /* libgmpxx.a */; }; + 5C67D2FE27CB9FA100E4261F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F727CB9FA100E4261F /* libffi.a */; }; + 5C67D2FF27CB9FA100E4261F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F727CB9FA100E4261F /* libffi.a */; }; + 5C67D30027CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */; }; + 5C67D30127CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */; }; + 5C67D30227CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */; }; + 5C67D30327CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */; }; 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; 5C6AD81427A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; 5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */; }; @@ -125,11 +125,6 @@ 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = ""; }; 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = ""; }; 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = ""; }; - 5C27D00327C7D8B500DD6182 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5C27D00427C7D8B500DD6182 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a"; sourceTree = ""; }; - 5C27D00627C7D8B500DD6182 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a"; sourceTree = ""; }; 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = ""; }; 5C2E260927A2C63500F70299 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; @@ -140,6 +135,11 @@ 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = ""; }; 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownHelp.swift; sourceTree = ""; }; + 5C67D2F527CB9FA100E4261F /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C67D2F627CB9FA100E4261F /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C67D2F727CB9FA100E4261F /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a"; sourceTree = ""; }; + 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a"; sourceTree = ""; }; 5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = ""; }; 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMetaView.swift; sourceTree = ""; }; 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavLinkPlain.swift; sourceTree = ""; }; @@ -186,13 +186,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C27D00827C7D8B500DD6182 /* libgmpxx.a in Frameworks */, - 5C27D01027C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */, - 5C27D00A27C7D8B500DD6182 /* libgmp.a in Frameworks */, + 5C67D30227CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */, 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */, + 5C67D2FE27CB9FA100E4261F /* libffi.a in Frameworks */, + 5C67D2FC27CB9FA100E4261F /* libgmpxx.a in Frameworks */, 5C764E83279C748B000C6508 /* libz.tbd in Frameworks */, - 5C27D00E27C7D8B500DD6182 /* libffi.a in Frameworks */, - 5C27D00C27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */, + 5C67D2FA27CB9FA100E4261F /* libgmp.a in Frameworks */, + 5C67D30027CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */, 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -201,13 +201,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C27D00927C7D8B500DD6182 /* libgmpxx.a in Frameworks */, - 5C27D00F27C7D8B500DD6182 /* libffi.a in Frameworks */, + 5C67D2FD27CB9FA100E4261F /* libgmpxx.a in Frameworks */, 5C764E85279C748C000C6508 /* libz.tbd in Frameworks */, - 5C27D00B27C7D8B500DD6182 /* libgmp.a in Frameworks */, - 5C27D01127C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */, - 5C27D00D27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */, + 5C67D2FB27CB9FA100E4261F /* libgmp.a in Frameworks */, + 5C67D2FF27CB9FA100E4261F /* libffi.a in Frameworks */, 5C764E84279C748C000C6508 /* libiconv.tbd in Frameworks */, + 5C67D30127CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */, + 5C67D30327CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -259,11 +259,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C27D00627C7D8B500DD6182 /* libffi.a */, - 5C27D00427C7D8B500DD6182 /* libgmp.a */, - 5C27D00327C7D8B500DD6182 /* libgmpxx.a */, - 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */, - 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */, + 5C67D2F727CB9FA100E4261F /* libffi.a */, + 5C67D2F527CB9FA100E4261F /* libgmp.a */, + 5C67D2F627CB9FA100E4261F /* libgmpxx.a */, + 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */, + 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */, ); path = Libraries; sourceTree = ""; @@ -833,6 +833,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ""; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; MARKETING_VERSION = 0.4; diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e753ed7dfb..08c2842af3 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -123,10 +123,7 @@ responseToView testView = \case CRMemberSubError g c e -> [ttyGroup' g <> " member " <> ttyContact c <> " error: " <> sShow e] CRMemberSubErrors summary -> viewErrorsSummary summary " group member errors" CRGroupSubscribed g -> [ttyFullGroup g <> ": connected to server(s)"] - CRPendingSubSummary summary -> - (if null subscribed then [] else [sShow (length subscribed) <> " pending connections subscribed"]) <> viewErrorsSummary errors " connection errors" - where - (errors, subscribed) = partition (isJust . connError) summary + CRPendingSubSummary _ -> [] CRSndFileSubError SndFileTransfer {fileId, fileName} e -> ["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] CRRcvFileSubError RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} e -> From 310f56a9b30535d0b91fd3627f60b15084bd4881 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sun, 27 Feb 2022 19:25:40 +0000 Subject: [PATCH 05/27] update version/build: ios 0.4 (15), android 0.3 (5) --- apps/android/app/build.gradle | 2 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 1a91671c05..ee66cd7c32 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -11,7 +11,7 @@ android { applicationId "chat.simplex.app" minSdk 26 targetSdk 32 - versionCode 4 + versionCode 5 versionName "0.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 09221d89a5..c87ab99d1b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -856,7 +856,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; From 0b00c2ad7612d30fcd96d24cafff7facecb51363 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:44:48 +0000 Subject: [PATCH 06/27] android: receiving messages in background; ios: background task completion (#382) * android: receiving messages in background; ios: background task completion * complete receiving and sending messages in background --- apps/android/app/build.gradle | 1 + .../java/chat/simplex/app/MainActivity.kt | 6 +- .../main/java/chat/simplex/app/SimplexApp.kt | 43 +++++++-- .../java/chat/simplex/app/model/BGManager.kt | 3 +- .../java/chat/simplex/app/model/ChatModel.kt | 3 +- .../java/chat/simplex/app/model/NtfManager.kt | 7 +- .../java/chat/simplex/app/model/SimpleXAPI.kt | 47 +++++----- .../chat/simplex/app/views/chat/ChatView.kt | 25 +++-- .../simplex/app/views/helpers/AlertManager.kt | 3 +- .../simplex/app/views/helpers/ModalView.kt | 3 +- .../app/views/newchat/QRCodeScanner.kt | 7 +- .../views/usersettings/MarkdownHelpView.kt | 12 +-- .../app/views/usersettings/SettingsView.kt | 27 ++---- apps/ios/Shared/Model/ChatModel.swift | 3 + apps/ios/Shared/Model/NtfManager.swift | 1 + apps/ios/Shared/Model/SimpleXAPI.swift | 92 +++++++++++++++++-- .../Views/UserSettings/MarkdownHelp.swift | 12 +-- 17 files changed, 199 insertions(+), 96 deletions(-) diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index ee66cd7c32..f129fd1784 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -71,6 +71,7 @@ dependencies { implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' + implementation 'androidx.lifecycle:lifecycle-process:2.4.1' implementation 'androidx.activity:activity-compose:1.4.0' implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.3.2' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index a41051877d..20fff5ec83 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -76,7 +76,7 @@ fun processIntent(intent: Intent?, chatModel: ChatModel) { when (intent?.action) { NtfManager.OpenChatAction -> { val chatId = intent.getStringExtra("chatId") - Log.d("SIMPLEX", "processIntent: OpenChatAction $chatId") + Log.d(TAG, "processIntent: OpenChatAction $chatId") if (chatId != null) { val cInfo = chatModel.getChat(chatId)?.chatInfo if (cInfo != null) withApi { openChat(chatModel, cInfo) } @@ -90,7 +90,7 @@ fun processIntent(intent: Intent?, chatModel: ChatModel) { } fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { - Log.d("SIMPLEX", "connectIfOpenedViaUri: opened via link") + Log.d(TAG, "connectIfOpenedViaUri: opened via link") if (chatModel.currentUser.value == null) { // TODO open from chat list view chatModel.appOpenUrl.value = uri @@ -102,7 +102,7 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { confirmText = "Connect", onConfirm = { withApi { - Log.d("SIMPLEX", "connectIfOpenedViaUri: connecting") + Log.d(TAG, "connectIfOpenedViaUri: connecting") connectViaUri(chatModel, action, uri) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index cb4f058c4b..0afefb1700 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -1,8 +1,10 @@ package chat.simplex.app import android.app.Application -import android.net.LocalServerSocket +import android.content.Context +import android.net.* import android.util.Log +import androidx.lifecycle.* import androidx.work.* import chat.simplex.app.model.* import chat.simplex.app.views.helpers.withApi @@ -13,6 +15,8 @@ import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import kotlin.concurrent.thread +const val TAG = "SIMPLEX" + // ghc's rts external fun initHS() // android-support @@ -24,7 +28,7 @@ external fun chatInit(path: String): ChatCtrl external fun chatSendCmd(ctrl: ChatCtrl, msg: String) : String external fun chatRecvMsg(ctrl: ChatCtrl) : String -class SimplexApp: Application() { +class SimplexApp: Application(), LifecycleEventObserver { private lateinit var controller: ChatController lateinit var chatModel: ChatModel private lateinit var ntfManager: NtfManager @@ -43,6 +47,8 @@ class SimplexApp: Application() { override fun onCreate() { super.onCreate() + ProcessLifecycleOwner.get().lifecycle.addObserver(this) + registerNetworkCallback() ntfManager = NtfManager(applicationContext) val ctrl = chatInit(applicationContext.filesDir.toString()) controller = ChatController(ctrl, ntfManager, applicationContext) @@ -53,18 +59,43 @@ class SimplexApp: Application() { } } + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + Log.d(TAG, "onStateChanged: $event") + } + + private fun registerNetworkCallback() { + val connectivityManager = getSystemService(ConnectivityManager::class.java) + connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + Log.e(TAG, "The default network is now: " + network) + } + + override fun onLost(network: Network) { + Log.e(TAG, "The application no longer has a default network. The last default network was " + network) + } + + override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + Log.e(TAG, "The default network changed capabilities: " + networkCapabilities) + } + + override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) { + Log.e(TAG, "The default network changed link properties: " + linkProperties) + } + }) + } + companion object { init { val socketName = "local.socket.address.listen.native.cmd2" val s = Semaphore(0) thread(name="stdout/stderr pipe") { - Log.d("SIMPLEX", "starting server") + Log.d(TAG, "starting server") val server = LocalServerSocket(socketName) - Log.d("SIMPLEX", "started server") + Log.d(TAG, "started server") s.release() val receiver = server.accept() - Log.d("SIMPLEX", "started receiver") + Log.d(TAG, "started receiver") val logbuffer = FifoQueue(500) if (receiver != null) { val inStream = receiver.inputStream @@ -73,7 +104,7 @@ class SimplexApp: Application() { while(true) { val line = input.readLine() ?: break - Log.d("SIMPLEX (stdout/stderr)", line) + Log.w("$TAG (stdout/stderr)", line) logbuffer.add(line) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt index a511bf06fe..3089a5ed0c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt @@ -3,6 +3,7 @@ package chat.simplex.app.model import android.content.Context import android.util.Log import androidx.work.* +import chat.simplex.app.TAG import chat.simplex.app.chatRecvMsg import java.util.concurrent.TimeUnit @@ -24,7 +25,7 @@ class BackgroundAPIWorker(appContext: Context, workerParams: WorkerParameters, c private fun getNewItems() { val json = chatRecvMsg(controller) val r = APIResponse.decodeStr(json).resp - Log.d("SIMPLEX", "chatRecvMsg: ${r.responseType}") + Log.d(TAG, "chatRecvMsg: ${r.responseType}") } private fun buildRequest(): OneTimeWorkRequest { diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 75b8c74220..96b08e86db 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.* import androidx.compose.ui.text.style.TextDecoration import chat.simplex.app.ui.theme.SecretColor +import chat.simplex.app.ui.theme.SimplexBlue import kotlinx.datetime.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -650,7 +651,7 @@ enum class FormatColor(val color: String) { val uiColor: Color @Composable get() = when (this) { red -> Color.Red green -> Color.Green - blue -> Color.Blue + blue -> SimplexBlue yellow -> Color.Yellow cyan -> Color.Cyan magenta -> Color.Magenta diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt index 114cf22bf6..f5b54fd8eb 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt @@ -6,8 +6,7 @@ import android.content.Intent import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import chat.simplex.app.MainActivity -import chat.simplex.app.R +import chat.simplex.app.* import kotlinx.datetime.Clock class NtfManager(val context: Context) { @@ -30,7 +29,7 @@ class NtfManager(val context: Context) { } fun notifyMessageReceived(cInfo: ChatInfo, cItem: ChatItem) { - Log.d("SIMPLEX", "notifyMessageReceived ${cInfo.id}") + Log.d(TAG, "notifyMessageReceived ${cInfo.id}") val now = Clock.System.now().toEpochMilliseconds() val recentNotification = (now - prevNtfTime.getOrDefault(cInfo.id, 0) < msgNtfTimeoutMs) prevNtfTime[cInfo.id] = now @@ -64,7 +63,7 @@ class NtfManager(val context: Context) { } private fun getMsgPendingIntent(cInfo: ChatInfo) : PendingIntent{ - Log.d("SIMPLEX", "getMsgPendingIntent ${cInfo.id}") + Log.d(TAG, "getMsgPendingIntent ${cInfo.id}") val uniqueInt = (System.currentTimeMillis() and 0xfffffff).toInt() val intent = Intent(context, MainActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 9e4b899f79..aac062974b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -5,8 +5,7 @@ import android.app.ActivityManager.RunningAppProcessInfo import android.content.Context import android.util.Log import androidx.compose.runtime.mutableStateOf -import chat.simplex.app.chatRecvMsg -import chat.simplex.app.chatSendCmd +import chat.simplex.app.* import chat.simplex.app.views.helpers.AlertManager import chat.simplex.app.views.helpers.withApi import kotlinx.coroutines.Dispatchers @@ -24,7 +23,7 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap var chatModel = ChatModel(this) suspend fun startChat(u: User) { - Log.d("SIMPLEX (user)", u.toString()) + Log.d(TAG, "user: $u") try { apiStartChat() chatModel.userAddress.value = apiGetUserAddress() @@ -32,9 +31,9 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap chatModel.currentUser = mutableStateOf(u) chatModel.userCreated.value = true startReceiver() - Log.d("SIMPLEX", "started chat") + Log.d(TAG, "started chat") } catch(e: Error) { - Log.d("SIMPLEX", "failed starting chat $e") + Log.e(TAG, "failed starting chat $e") throw e } } @@ -62,11 +61,11 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap val c = cmd.cmdString chatModel.terminalItems.add(TerminalItem.cmd(cmd)) val json = chatSendCmd(ctrl, c) - Log.d("SIMPLEX", "sendCmd: ${cmd.cmdType}") + Log.d(TAG, "sendCmd: ${cmd.cmdType}") val r = APIResponse.decodeStr(json) - Log.d("SIMPLEX", "sendCmd response type ${r.resp.responseType}") + Log.d(TAG, "sendCmd response type ${r.resp.responseType}") if (r.resp is CR.Response || r.resp is CR.Invalid) { - Log.d("SIMPLEX", "sendCmd response json $json") + Log.d(TAG, "sendCmd response json $json") } chatModel.terminalItems.add(TerminalItem.resp(r.resp)) r.resp @@ -77,8 +76,8 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap return withContext(Dispatchers.IO) { val json = chatRecvMsg(ctrl) val r = APIResponse.decodeStr(json).resp - Log.d("SIMPLEX", "chatRecvMsg: ${r.responseType}") - if (r is CR.Response || r is CR.Invalid) Log.d("SIMPLEX", "chatRecvMsg json: $json") + Log.d(TAG, "chatRecvMsg: ${r.responseType}") + if (r is CR.Response || r is CR.Invalid) Log.d(TAG, "chatRecvMsg json: $json") r } } @@ -91,7 +90,7 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap suspend fun apiGetActiveUser(): User? { val r = sendCmd(CC.ShowActiveUser()) if (r is CR.ActiveUser) return r.user - Log.d("SIMPLEX", "apiGetActiveUser: ${r.responseType} ${r.details}") + Log.d(TAG, "apiGetActiveUser: ${r.responseType} ${r.details}") chatModel.userCreated.value = false return null } @@ -99,7 +98,7 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap suspend fun apiCreateActiveUser(p: Profile): User { val r = sendCmd(CC.CreateActiveUser(p)) if (r is CR.ActiveUser) return r.user - Log.d("SIMPLEX", "apiCreateActiveUser: ${r.responseType} ${r.details}") + Log.d(TAG, "apiCreateActiveUser: ${r.responseType} ${r.details}") throw Error("user not created ${r.responseType} ${r.details}") } @@ -118,21 +117,21 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap suspend fun apiGetChat(type: ChatType, id: Long): Chat? { val r = sendCmd(CC.ApiGetChat(type, id)) if (r is CR.ApiChat ) return r.chat - Log.d("SIMPLEX", "apiGetChat bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiGetChat bad response: ${r.responseType} ${r.details}") return null } suspend fun apiSendMessage(type: ChatType, id: Long, mc: MsgContent): AChatItem? { val r = sendCmd(CC.ApiSendMessage(type, id, mc)) if (r is CR.NewChatItem ) return r.chatItem - Log.d("SIMPLEX", "apiSendMessage bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiSendMessage bad response: ${r.responseType} ${r.details}") return null } suspend fun apiAddContact(): String? { val r = sendCmd(CC.AddContact()) if (r is CR.Invitation) return r.connReqInvitation - Log.d("SIMPLEX", "apiAddContact bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiAddContact bad response: ${r.responseType} ${r.details}") return null } @@ -182,21 +181,21 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap val r = sendCmd(CC.UpdateProfile(profile)) if (r is CR.UserProfileNoChange) return profile if (r is CR.UserProfileUpdated) return r.toProfile - Log.d("SIMPLEX", "apiUpdateProfile bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}") return null } suspend fun apiCreateUserAddress(): String? { val r = sendCmd(CC.CreateMyAddress()) if (r is CR.UserContactLinkCreated) return r.connReqContact - Log.d("SIMPLEX", "apiCreateUserAddress bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiCreateUserAddress bad response: ${r.responseType} ${r.details}") return null } suspend fun apiDeleteUserAddress(): Boolean { val r = sendCmd(CC.DeleteMyAddress()) if (r is CR.UserContactLinkDeleted) return true - Log.d("SIMPLEX", "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}") return false } @@ -207,34 +206,34 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap && r.chatError.storeError is StoreError.UserContactLinkNotFound) { return null } - Log.d("SIMPLEX", "apiGetUserAddress bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiGetUserAddress bad response: ${r.responseType} ${r.details}") return null } suspend fun apiAcceptContactRequest(contactReqId: Long): Contact? { val r = sendCmd(CC.ApiAcceptContact(contactReqId)) if (r is CR.AcceptingContactRequest) return r.contact - Log.d("SIMPLEX", "apiAcceptContactRequest bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiAcceptContactRequest bad response: ${r.responseType} ${r.details}") return null } suspend fun apiRejectContactRequest(contactReqId: Long): Boolean { val r = sendCmd(CC.ApiRejectContact(contactReqId)) if (r is CR.ContactRequestRejected) return true - Log.d("SIMPLEX", "apiRejectContactRequest bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiRejectContactRequest bad response: ${r.responseType} ${r.details}") return false } suspend fun apiChatRead(type: ChatType, id: Long, range: CC.ItemRange): Boolean { val r = sendCmd(CC.ApiChatRead(type, id, range)) if (r is CR.CmdOk) return true - Log.d("SIMPLEX", "apiChatRead bad response: ${r.responseType} ${r.details}") + Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}") return false } fun apiErrorAlert(method: String, title: String, r: CR) { val errMsg = "${r.responseType}: ${r.details}" - Log.e("SIMPLEX", "$method bad response: $errMsg") + Log.e(TAG, "$method bad response: $errMsg") AlertManager.shared.showAlertMsg(title, errMsg) } @@ -286,7 +285,7 @@ open class ChatController(val ctrl: ChatCtrl, val ntfManager: NtfManager, val ap // NtfManager.shared.notifyMessageReceived(cInfo, cItem) // } else -> - Log.d("SIMPLEX" , "unsupported event: ${r.responseType}") + Log.d(TAG , "unsupported event: ${r.responseType}") } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 774afd42ef..d509cd97b3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -18,6 +18,7 @@ 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.TAG import chat.simplex.app.model.* import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.item.ChatItemView @@ -39,7 +40,7 @@ fun ChatView(chatModel: ChatModel) { BackHandler { chatModel.chatId.value = null } // TODO a more advanced version would mark as read only if in view LaunchedEffect(chat.chatItems) { - Log.d("SIMPLEX", "ChatView ${chatModel.chatId.value}: LaunchedEffect") + Log.d(TAG, "ChatView ${chatModel.chatId.value}: LaunchedEffect") delay(1000L) if (chat.chatItems.count() > 0) { chatModel.markChatItemsRead(chat.chatInfo) @@ -79,18 +80,16 @@ fun ChatLayout( info: () -> Unit, sendMessage: (String) -> Unit ) { - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { - Scaffold( - topBar = { ChatInfoToolbar(chat, back, info) }, - bottomBar = { SendMsgView(sendMessage) }, - modifier = Modifier.navigationBarsWithImePadding() - ) { contentPadding -> - Surface( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colors.background) - ) { - ChatItemsList(chatItems) + Surface(Modifier.fillMaxWidth().background(MaterialTheme.colors.background)) { + ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { + Scaffold( + topBar = { ChatInfoToolbar(chat, back, info) }, + bottomBar = { SendMsgView(sendMessage) }, + modifier = Modifier.navigationBarsWithImePadding() + ) { contentPadding -> + Box(Modifier.padding(contentPadding)) { + ChatItemsList(chatItems) + } } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt index 610ffc949b..f431e68e67 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt @@ -4,13 +4,14 @@ import android.util.Log import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf +import chat.simplex.app.TAG class AlertManager { var alertView = mutableStateOf<(@Composable () -> Unit)?>(null) var presentAlert = mutableStateOf(false) fun showAlert(alert: @Composable () -> Unit) { - Log.d("SIMPLEX", "AlertManager.showAlert") + Log.d(TAG, "AlertManager.showAlert") alertView.value = alert presentAlert.value = true } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt index e0401c7099..bb10bd639f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import chat.simplex.app.TAG import chat.simplex.app.views.helpers.CloseSheetBar @Composable @@ -36,7 +37,7 @@ class ModalManager { } fun showCustomModal(modal: @Composable (close: () -> Unit) -> Unit) { - Log.d("SIMPLEX", "ModalManager.showModal") + Log.d(TAG, "ModalManager.showModal") modalViews.add(modal) modalCount.value = modalViews.count() } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt index 8f2b940258..f6bee70490 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat +import chat.simplex.app.TAG import com.google.common.util.concurrent.ListenableFuture import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning @@ -62,7 +63,7 @@ fun QRCodeScanner(onBarcode: (String) -> Unit) { cameraProvider.unbindAll() cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis) } catch (e: Exception) { - Log.d("SIMPLEX", "CameraPreview: ${e.localizedMessage}") + Log.d(TAG, "CameraPreview: ${e.localizedMessage}") } }, ContextCompat.getMainExecutor(context)) } @@ -90,11 +91,11 @@ class BarCodeAnalyser( if (barcodes.isNotEmpty()) { onBarcodeDetected(barcodes) } else { - Log.d("SIMPLEX", "BarcodeAnalyser: No barcode Scanned") + Log.d(TAG, "BarcodeAnalyser: No barcode Scanned") } } .addOnFailureListener { exception -> - Log.d("SIMPLEX", "BarcodeAnalyser: Something went wrong $exception") + Log.e(TAG, "BarcodeAnalyser: Something went wrong $exception") } .addOnCompleteListener { image.close() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt index 40def46dba..0f0a649f1e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt @@ -25,14 +25,14 @@ fun MarkdownHelpView() { "You can use markdown to format messages:", Modifier.padding(vertical = 16.dp) ) - MdFormat("*bold*", "bold text", Format.Bold()) - MdFormat("_italic_", "italic text", Format.Italic()) - MdFormat("~strike~", "strikethrough text", Format.StrikeThrough()) - MdFormat("`code`", "a = b + c", Format.Snippet()) + MdFormat("*bold*", "bold", Format.Bold()) + MdFormat("_italic_", "italic", Format.Italic()) + MdFormat("~strike~", "strike", Format.StrikeThrough()) + MdFormat("`a + b`", "a + b", Format.Snippet()) Row { MdSyntax("!1 colored!") Text(buildAnnotatedString { - withStyle(Format.Colored(FormatColor.red).style) { append("red text") } + withStyle(Format.Colored(FormatColor.red).style) { append("colored") } append(" (") appendColor(this, "1", FormatColor.red, ", ") appendColor(this, "2", FormatColor.green, ", ") @@ -46,7 +46,7 @@ fun MarkdownHelpView() { MdSyntax("#secret#") SelectionContainer { Text(buildAnnotatedString { - withStyle(Format.Secret().style) { append("secret text") } + withStyle(Format.Secret().style) { append("secret") } }) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index 0e55619137..b9b4e69cc5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -30,10 +30,7 @@ fun SettingsView(chatModel: ChatModel) { if (user != null) { SettingsLayout( profile = user.profile, -// showModal = { modal -> ModalManager.shared.showModal { modal(chatModel) } }, - showProfile = { ModalManager.shared.showModal { UserProfileView(chatModel) } }, - showAddress = { ModalManager.shared.showModal { UserAddressView(chatModel) } }, - showHelp = { ModalManager.shared.showModal { HelpView(chatModel) } }, + showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } }, showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } } ) } @@ -45,10 +42,7 @@ val simplexTeamUri = @Composable fun SettingsLayout( profile: Profile, -// showModal: (modal: @Composable (ChatModel) -> Unit) -> Unit, - showProfile: () -> Unit, - showAddress: () -> Unit, - showHelp: () -> Unit, + showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), showTerminal: () -> Unit ) { val uriHandler = LocalUriHandler.current @@ -67,10 +61,11 @@ fun SettingsLayout( Text( "Your Settings", style = MaterialTheme.typography.h1, + modifier = Modifier.padding(start = 8.dp) ) Spacer(Modifier.height(30.dp)) - SettingsSectionView(showProfile, 60.dp) { + SettingsSectionView(showModal { UserProfileView(it) }, 60.dp) { Icon( Icons.Outlined.AccountCircle, contentDescription = "Avatar Placeholder", @@ -86,7 +81,7 @@ fun SettingsLayout( } } Divider(Modifier.padding(horizontal = 8.dp)) - SettingsSectionView(showAddress) { + SettingsSectionView(showModal { UserAddressView(it) }) { Icon( Icons.Outlined.QrCode, contentDescription = "Address", @@ -96,7 +91,7 @@ fun SettingsLayout( } Spacer(Modifier.height(24.dp)) - SettingsSectionView(showHelp) { + SettingsSectionView(showModal { HelpView(it) }) { Icon( Icons.Outlined.HelpOutline, contentDescription = "Chat help", @@ -104,7 +99,7 @@ fun SettingsLayout( Spacer(Modifier.padding(horizontal = 4.dp)) Text("How to use SimpleX Chat") } - SettingsSectionView({ ModalManager.shared.showModal { MarkdownHelpView() } }) { + SettingsSectionView(showModal { MarkdownHelpView() }) { Icon( Icons.Outlined.TextFormat, contentDescription = "Markdown help", @@ -167,12 +162,12 @@ fun SettingsLayout( } @Composable -fun SettingsSectionView(func: () -> Unit, height: Dp = 48.dp, content: (@Composable () -> Unit)) { +fun SettingsSectionView(click: () -> Unit, height: Dp = 48.dp, content: (@Composable () -> Unit)) { Row( Modifier .padding(start = 8.dp) .fillMaxWidth() - .clickable(onClick = func) + .clickable(onClick = click) .height(height), verticalAlignment = Alignment.CenterVertically ) { @@ -191,9 +186,7 @@ fun PreviewSettingsLayout() { SimpleXTheme { SettingsLayout( profile = Profile.sampleData, - showProfile = {}, - showAddress = {}, - showHelp = {}, + showModal = {{}}, showTerminal = {} ) } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index c8d37b3b65..d3d80e070c 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -22,6 +22,9 @@ final class ChatModel: ObservableObject { @Published var terminalItems: [TerminalItem] = [] @Published var userAddress: String? @Published var appOpenUrl: URL? + + var messageDelivery: Dictionary Void> = [:] + static let shared = ChatModel() func hasChat(_ id: String) -> Bool { diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 5459d9b673..3f0bbca448 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -178,6 +178,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { if let s = body { content.body = s } content.targetContentIdentifier = targetContentIdentifier content.userInfo = userInfo + // TODO move logic of adding sound here, so it applies to background notifications too content.sound = .default // content.interruptionLevel = .active // content.relevanceScore = 0.5 // 0-1 diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 16b38c19b9..a6a45a8f1c 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -241,10 +241,53 @@ enum TerminalItem: Identifiable { } } -func chatSendCmdSync(_ cmd: ChatCommand) -> ChatResponse { +private func _sendCmd(_ cmd: ChatCommand) -> ChatResponse { var c = cmd.cmdString.cString(using: .utf8)! + return chatResponse(chat_send_cmd(getChatCtrl(), &c)) +} + +private func beginBGTask(_ handler: (() -> Void)? = nil) -> (() -> Void) { + var id: UIBackgroundTaskIdentifier! + var running = true + let endTask = { +// logger.debug("beginBGTask: endTask \(id.rawValue)") + if running { + running = false + if let h = handler { +// logger.debug("beginBGTask: user handler") + h() + } + if id != .invalid { + UIApplication.shared.endBackgroundTask(id) + id = .invalid + } + } + } + id = UIApplication.shared.beginBackgroundTask(expirationHandler: endTask) +// logger.debug("beginBGTask: \(id.rawValue)") + return endTask +} + +let msgDelay: Double = 7.5 +let maxTaskDuration: Double = 15 + +private func withBGTask(bgDelay: Double? = nil, f: @escaping () -> ChatResponse) -> ChatResponse { + let endTask = beginBGTask() + DispatchQueue.global().asyncAfter(deadline: .now() + maxTaskDuration, execute: endTask) + let r = f() + if let d = bgDelay { + DispatchQueue.global().asyncAfter(deadline: .now() + d, execute: endTask) + } else { + endTask() + } + return r +} + +func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil) -> ChatResponse { logger.debug("chatSendCmd \(cmd.cmdType)") - let resp = chatResponse(chat_send_cmd(getChatCtrl(), &c)) + let resp = bgTask + ? withBGTask(bgDelay: bgDelay) { _sendCmd(cmd) } + : _sendCmd(cmd) logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)") if case let .response(_, json) = resp { logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)") @@ -256,16 +299,19 @@ func chatSendCmdSync(_ cmd: ChatCommand) -> ChatResponse { return resp } -func chatSendCmd(_ cmd: ChatCommand) async -> ChatResponse { +func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil) async -> ChatResponse { await withCheckedContinuation { cont in - cont.resume(returning: chatSendCmdSync(cmd)) + cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay)) } } func chatRecvMsg() async -> ChatResponse { await withCheckedContinuation { cont in - let resp = chatResponse(chat_recv_msg(getChatCtrl())!) - cont.resume(returning: resp) + _ = withBGTask(bgDelay: msgDelay) { + let resp = chatResponse(chat_recv_msg(getChatCtrl())!) + cont.resume(returning: resp) + return resp + } } } @@ -304,13 +350,30 @@ func apiGetChat(type: ChatType, id: Int64) async throws -> Chat { } func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) async throws -> ChatItem { - let r = await chatSendCmd(.apiSendMessage(type: type, id: id, msg: msg)) - if case let .newChatItem(aChatItem) = r { return aChatItem.chatItem } + let chatModel = ChatModel.shared + let cmd = ChatCommand.apiSendMessage(type: type, id: id, msg: msg) + let r: ChatResponse + if type == .direct { + var cItem: ChatItem! + let endTask = beginBGTask({ if cItem != nil { chatModel.messageDelivery.removeValue(forKey: cItem.id) } }) + r = await chatSendCmd(cmd, bgTask: false) + if case let .newChatItem(aChatItem) = r { + cItem = aChatItem.chatItem + chatModel.messageDelivery[cItem.id] = endTask + return cItem + } + endTask() + } else { + r = await chatSendCmd(cmd, bgDelay: msgDelay) + if case let .newChatItem(aChatItem) = r { + return aChatItem.chatItem + } + } throw r } func apiAddContact() throws -> String { - let r = chatSendCmdSync(.addContact) + let r = chatSendCmdSync(.addContact, bgTask: false) if case let .invitation(connReqInvitation) = r { return connReqInvitation } throw r } @@ -325,7 +388,7 @@ func apiConnect(connReq: String) async throws { } func apiDeleteChat(type: ChatType, id: Int64) async throws { - let r = await chatSendCmd(.apiDeleteChat(type: type, id: id)) + let r = await chatSendCmd(.apiDeleteChat(type: type, id: id), bgTask: false) if case .contactDeleted = r { return } throw r } @@ -450,6 +513,8 @@ class ChatReceiver { self._lastMsgTime = .now processReceivedMsg(msg) if self.receiveMessages { + do { try await Task.sleep(nanoseconds: 7_500_000) } + catch { logger.error("receiveMsgLoop: Task.sleep error: \(error.localizedDescription)") } await receiveMsgLoop() } } @@ -508,6 +573,13 @@ func processReceivedMsg(_ res: ChatResponse) { let cItem = aChatItem.chatItem if chatModel.upsertChatItem(cInfo, cItem) { NtfManager.shared.notifyMessageReceived(cInfo, cItem) + } else if let endTask = chatModel.messageDelivery[cItem.id] { + switch cItem.meta.itemStatus { + case .sndSent: endTask() + case .sndErrorAuth: endTask() + case .sndError: endTask() + default: break + } } default: logger.debug("unsupported event: \(res.responseType)") diff --git a/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift index d9a73372dd..855d0adff1 100644 --- a/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift @@ -13,13 +13,13 @@ struct MarkdownHelp: View { VStack(alignment: .leading, spacing: 8) { Text("You can use markdown to format messages:") .padding(.bottom) - mdFormat("*bold*", Text("bold text").bold()) - mdFormat("_italic_", Text("italic text").italic()) - mdFormat("~strike~", Text("strikethrough text").strikethrough()) - mdFormat("`code`", Text("`a = b + c`").font(.body.monospaced())) - mdFormat("!1 colored!", Text("red text").foregroundColor(.red) + Text(" (") + color("1", .red) + color("2", .green) + color("3", .blue) + color("4", .yellow) + color("5", .cyan) + Text("6").foregroundColor(.purple) + Text(")")) + mdFormat("*bold*", Text("bold").bold()) + mdFormat("_italic_", Text("italic").italic()) + mdFormat("~strike~", Text("strike").strikethrough()) + mdFormat("`a + b`", Text("`a + b`").font(.body.monospaced())) + mdFormat("!1 colored!", Text("colored").foregroundColor(.red) + Text(" (") + color("1", .red) + color("2", .green) + color("3", .blue) + color("4", .yellow) + color("5", .cyan) + Text("6").foregroundColor(.purple) + Text(")")) ( - mdFormat("#secret#", Text("secret text") + mdFormat("#secret#", Text("secret") .foregroundColor(.clear) .underline(color: .primary) + Text(" (can be copied)")) ) From 8d057613f53bcf961e37af46c827fbefd923eb4e Mon Sep 17 00:00:00 2001 From: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com> Date: Mon, 28 Feb 2022 16:27:55 +0400 Subject: [PATCH 07/27] core: update default servers (#385) --- src/Simplex/Chat/Options.hs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index 1d4f570b8f..06b90edaa0 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -28,10 +28,9 @@ data ChatOpts = ChatOpts defaultSMPServers :: NonEmpty SMPServer defaultSMPServers = L.fromList - [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im", - "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im", - "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im" - -- "smp://Tn1b3Rr7_gErbVt2v50Y_T-PvUAi1BYAMS-62w-k9CI=@139.162.240.237" + [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im", + "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im", + "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im" ] chatOpts :: FilePath -> Parser ChatOpts @@ -51,8 +50,7 @@ chatOpts appDir = <> short 's' <> metavar "SERVER" <> help - "Comma separated list of SMP server(s) to use \ - \(default: smp4.simplex.im,smp5.simplex.im,smp6.simplex.im)" + "Comma separated list of SMP server(s) to use" <> value defaultSMPServers ) <*> switch From 326a2a1877db8cea9559606b6c49cd4650c53525 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 28 Feb 2022 13:52:20 +0000 Subject: [PATCH 08/27] ci: add x86 packages (#383) --- flake.nix | 232 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 163 insertions(+), 69 deletions(-) diff --git a/flake.nix b/flake.nix index a827eabc56..b3b7949334 100644 --- a/flake.nix +++ b/flake.nix @@ -45,76 +45,170 @@ "exe:simplex-chat" = (drv pkgs).simplex-chat.components.exes.simplex-chat; } // ({ "x86_64-linux" = - let - androidPkgs = pkgs.pkgsCross.aarch64-android; - # For some reason building libiconv with nixpgks android setup produces - # LANGINFO_CODESET to be found, which is not compatible with android sdk 23; - # so we'll patch up iconv to not include that. - androidIconv = (androidPkgs.libiconv.override { enableStatic = true; }).overrideAttrs (old: { - postConfigure = '' - echo "#undef HAVE_LANGINFO_CODESET" >> libcharset/config.h - echo "#undef HAVE_LANGINFO_CODESET" >> lib/config.h - ''; - }); - # Similarly to icovn, for reasons beyond my current knowledge, nixpkgs andorid - # toolchain makes configure believe we have MEMFD_CREATE, which we don't in - # sdk 23. - androidFFI = androidPkgs.libffi.overrideAttrs (old: { - dontDisableStatic = true; - hardeningDisable = [ "fortify" ]; - postConfigure = '' - echo "#undef HAVE_MEMFD_CREATE" >> aarch64-unknown-linux-android/fficonfig.h - ''; - } - );in { - "aarch64-android:lib:support" = (drv androidPkgs).android-support.components.library.override { - smallAddressSpace = true; enableShared = false; - setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ]; - postInstall = '' - - mkdir -p $out/_pkg - cp libsupport.so $out/_pkg - ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsupport.so - (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-android-libsupport.zip *) - rm -fR $out/_pkg - - mkdir -p $out/nix-support - echo "file binary-dist \"$(echo $out/*.zip)\"" \ - > $out/nix-support/hydra-build-products - ''; - }; - "aarch64-android:lib:simplex-chat" = (drv androidPkgs).simplex-chat.components.library.override { - smallAddressSpace = true; enableShared = false; - # for android we build a shared library, passing these arguments is a bit tricky, as - # we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for - # template haskell cross compilation. Thus we just pass them as linker options (-optl). - setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"]; - postInstall = '' - ${pkgs.tree}/bin/tree $out - mkdir -p $out/_pkg - # copy over includes, we might want those, but maybe not. - # cp -r $out/lib/*/*/include $out/_pkg/ - # find the libHS...ghc-X.Y.Z.a static library; this is the - # rolled up one with all dependencies included. - cp libsimplex.so $out/_pkg - # find ./dist -name "lib*.so" -exec cp {} $out/_pkg \; - # find ./dist -name "libHS*-ghc*.a" -exec cp {} $out/_pkg \; - # find ${androidFFI}/lib -name "*.a" -exec cp {} $out/_pkg \; - # find ${androidPkgs.gmp6.override { withStatic = true; }}/lib -name "*.a" -exec cp {} $out/_pkg \; - # find ${androidIconv}/lib -name "*.a" -exec cp {} $out/_pkg \; - # find ${androidPkgs.stdenv.cc.libc}/lib -name "*.a" -exec cp {} $out/_pkg \; - - ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsimplex.so - - ${pkgs.tree}/bin/tree $out/_pkg - (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-android-libsimplex.zip *) - rm -fR $out/_pkg - mkdir -p $out/nix-support - echo "file binary-dist \"$(echo $out/*.zip)\"" \ - > $out/nix-support/hydra-build-products + let + androidPkgs = pkgs.pkgsCross.aarch64-android; + # For some reason building libiconv with nixpgks android setup produces + # LANGINFO_CODESET to be found, which is not compatible with android sdk 23; + # so we'll patch up iconv to not include that. + androidIconv = (androidPkgs.libiconv.override { enableStatic = true; }).overrideAttrs (old: { + postConfigure = '' + echo "#undef HAVE_LANGINFO_CODESET" >> libcharset/config.h + echo "#undef HAVE_LANGINFO_CODESET" >> lib/config.h ''; - }; - }; + }); + # Similarly to icovn, for reasons beyond my current knowledge, nixpkgs andorid + # toolchain makes configure believe we have MEMFD_CREATE, which we don't in + # sdk 23. + androidFFI = androidPkgs.libffi.overrideAttrs (old: { + dontDisableStatic = true; + hardeningDisable = [ "fortify" ]; + postConfigure = '' + echo "#undef HAVE_MEMFD_CREATE" >> aarch64-unknown-linux-android/fficonfig.h + ''; + } + );in { + "aarch64-android:lib:support" = (drv androidPkgs).android-support.components.library.override { + smallAddressSpace = true; enableShared = false; + setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ]; + postInstall = '' + + mkdir -p $out/_pkg + cp libsupport.so $out/_pkg + ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsupport.so + (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-aarch64-android-libsupport.zip *) + rm -fR $out/_pkg + + mkdir -p $out/nix-support + echo "file binary-dist \"$(echo $out/*.zip)\"" \ + > $out/nix-support/hydra-build-products + ''; + }; + "aarch64-android:lib:simplex-chat" = (drv androidPkgs).simplex-chat.components.library.override { + smallAddressSpace = true; enableShared = false; + # for android we build a shared library, passing these arguments is a bit tricky, as + # we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for + # template haskell cross compilation. Thus we just pass them as linker options (-optl). + setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"]; + postInstall = '' + ${pkgs.tree}/bin/tree $out + mkdir -p $out/_pkg + # copy over includes, we might want those, but maybe not. + # cp -r $out/lib/*/*/include $out/_pkg/ + # find the libHS...ghc-X.Y.Z.a static library; this is the + # rolled up one with all dependencies included. + cp libsimplex.so $out/_pkg + # find ./dist -name "lib*.so" -exec cp {} $out/_pkg \; + # find ./dist -name "libHS*-ghc*.a" -exec cp {} $out/_pkg \; + # find ${androidFFI}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidPkgs.gmp6.override { withStatic = true; }}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidIconv}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidPkgs.stdenv.cc.libc}/lib -name "*.a" -exec cp {} $out/_pkg \; + + ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsimplex.so + + ${pkgs.tree}/bin/tree $out/_pkg + (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-aarch64-android-libsimplex.zip *) + rm -fR $out/_pkg + mkdir -p $out/nix-support + echo "file binary-dist \"$(echo $out/*.zip)\"" \ + > $out/nix-support/hydra-build-products + ''; + }; + "x86_64-android:lib:support" = (drv androidPkgs).android-support.components.library.override { + smallAddressSpace = true; enableShared = false; + setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ]; + postInstall = '' + + mkdir -p $out/_pkg + cp libsupport.so $out/_pkg + ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsupport.so + (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-x86_64-android-libsupport.zip *) + rm -fR $out/_pkg + + mkdir -p $out/nix-support + echo "file binary-dist \"$(echo $out/*.zip)\"" \ + > $out/nix-support/hydra-build-products + ''; + }; + "x86_64-android:lib:simplex-chat" = (drv androidPkgs).simplex-chat.components.library.override { + smallAddressSpace = true; enableShared = false; + # for android we build a shared library, passing these arguments is a bit tricky, as + # we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for + # template haskell cross compilation. Thus we just pass them as linker options (-optl). + setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"]; + postInstall = '' + ${pkgs.tree}/bin/tree $out + mkdir -p $out/_pkg + # copy over includes, we might want those, but maybe not. + # cp -r $out/lib/*/*/include $out/_pkg/ + # find the libHS...ghc-X.Y.Z.a static library; this is the + # rolled up one with all dependencies included. + cp libsimplex.so $out/_pkg + # find ./dist -name "lib*.so" -exec cp {} $out/_pkg \; + # find ./dist -name "libHS*-ghc*.a" -exec cp {} $out/_pkg \; + # find ${androidFFI}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidPkgs.gmp6.override { withStatic = true; }}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidIconv}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidPkgs.stdenv.cc.libc}/lib -name "*.a" -exec cp {} $out/_pkg \; + + ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsimplex.so + + ${pkgs.tree}/bin/tree $out/_pkg + (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-x86_64-android-libsimplex.zip *) + rm -fR $out/_pkg + mkdir -p $out/nix-support + echo "file binary-dist \"$(echo $out/*.zip)\"" \ + > $out/nix-support/hydra-build-products + ''; + }; + "x86_64-linux:lib:support" = (drv androidPkgs).android-support.components.library.override { + smallAddressSpace = true; enableShared = false; + setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ]; + postInstall = '' + + mkdir -p $out/_pkg + cp libsupport.so $out/_pkg + ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsupport.so + (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-x86_64-linux-libsupport.zip *) + rm -fR $out/_pkg + + mkdir -p $out/nix-support + echo "file binary-dist \"$(echo $out/*.zip)\"" \ + > $out/nix-support/hydra-build-products + ''; + }; + "x86_64-linux:lib:simplex-chat" = (drv androidPkgs).simplex-chat.components.library.override { + smallAddressSpace = true; enableShared = false; + # for android we build a shared library, passing these arguments is a bit tricky, as + # we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for + # template haskell cross compilation. Thus we just pass them as linker options (-optl). + setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"]; + postInstall = '' + ${pkgs.tree}/bin/tree $out + mkdir -p $out/_pkg + # copy over includes, we might want those, but maybe not. + # cp -r $out/lib/*/*/include $out/_pkg/ + # find the libHS...ghc-X.Y.Z.a static library; this is the + # rolled up one with all dependencies included. + cp libsimplex.so $out/_pkg + # find ./dist -name "lib*.so" -exec cp {} $out/_pkg \; + # find ./dist -name "libHS*-ghc*.a" -exec cp {} $out/_pkg \; + # find ${androidFFI}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidPkgs.gmp6.override { withStatic = true; }}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidIconv}/lib -name "*.a" -exec cp {} $out/_pkg \; + # find ${androidPkgs.stdenv.cc.libc}/lib -name "*.a" -exec cp {} $out/_pkg \; + + ${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsimplex.so + + ${pkgs.tree}/bin/tree $out/_pkg + (cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-x86_64-linux-libsimplex.zip *) + rm -fR $out/_pkg + mkdir -p $out/nix-support + echo "file binary-dist \"$(echo $out/*.zip)\"" \ + > $out/nix-support/hydra-build-products + ''; + }; + }; "aarch64-darwin" = { "aarch64-darwin:lib:simplex-chat" = (drv pkgs).simplex-chat.components.library.override { smallAddressSpace = true; enableShared = false; From 4585c7b649e64611738109cfabfdf8d00ffa7813 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 28 Feb 2022 14:31:55 +0000 Subject: [PATCH 09/27] ios: update build version (16) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index c87ab99d1b..3e63cf705c 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -856,7 +856,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; From b19cf35d2846d4212d65ae7d63d3eae5948c29e0 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 28 Feb 2022 20:45:31 +0000 Subject: [PATCH 10/27] iOS: v1 AppStore release (#386) * fix: disable create when display name is empty * update version/build 1.0 (17) * update build number (18) * terms and privacy policy --- PRIVACY.md | 89 ++++++++++++++++++++++ apps/ios/Shared/Views/WelcomeView.swift | 3 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 8 +- 3 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 PRIVACY.md diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000000..c0e8dbbd56 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,89 @@ +# SimpleX Chat Terms & Privacy Policy + +SimpleX Chat is the first chat platform that is 100% private by design - not only it has no access to your messages (thanks to open-source double-ratchet end-to-end encryption protocol and additional encryption layers), it also has no access to your profile and contacts - we do not have access to your connections graph. + +## Privacy Policy + +SimpleX Chat Ltd. ("SimpleX Chat") uses the best industry practices for security and end-to-end encryption to provide secure end-to-end encrypted messaging via private connections. SimpleX Chat is built on top of SimpleX messaging and application platform that uses a new message routing protocol that allows establishing private connection without having any kind of addresses that identify its users - we don't use emails, phone numbers, usernames, identity keys or any other user identifiers to pass messages between the users. + +### Information you provide + +We do not store user profiles. The profile you create in the app is local to your device. When you create a user profile, no records are created on our servers, and we have no access to any part of your profile information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all the data and the private connections you create with other users. + +Messages. SimpleX Chat cannot decrypt or otherwise access the content or size of your messages (each message is padded to a fixed size of 16kb). SimpleX Chat temporarily stores end-to-end encrypted messages on its servers for delivery to the devices that are temporarily offline. Your message history is stored only on your own devices. + +Connections with other users. When you create a connection with another user, two messaging queues are created on our servers (we use separate queues for direct and response messages, that can be on two different servers), or on the servers that you configured in the app, in case it allows such configuration. At the time of updating this document only our terminal app allows configuring the servers, our mobile apps will allow such configuration in the near future. Our servers do not store information about which queues are linked to your profile on the device, and they do not have any information in common that allow us to establish that these queues are related to your device or your profile - the access to each queue is authorized by a set of unique encryption keys, different for each queue, and separate for sender and recipient of the messages that are transmitted through the queue. + +Additional technical information can be stored on our servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX Chat limits this additional technical information to the minimum required to operate the Services. + +User Support. If you contact SimpleX Chat any personal data you may share with us is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support via chat, when it is possible. + +### Information we may share + +We operate our Services using third parties. While we do not share any user data, these third party may access the encrypted user data as it is stored or transmitted via our servers. + +We use Third party to provide email services - if you ask for support via email, your and SimpleX Chat email providers may access these emails according their privacy policies and terms of service. + +The cases when SimpleX Chat may need to share the data we temporarily store on the servers: + +- To meet any applicable law, regulation, legal process or enforceable governmental request. +- To enforce applicable Terms, including investigation of potential violations. +- To detect, prevent, or otherwise address fraud, security, or technical issues. +- To protect against harm to the rights, property, or safety of SimpleX Chat, our users, or the public as required or permitted by law. +Updates + +We will update this privacy policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our Services confirms your acceptance of our updated Privacy Policy. + +Please also read our Terms of Service. + +If you have questions about our Privacy Policy please contact us at chat@simplex.chat. + +## Terms of Service + +You accept to our Terms of Service ("Terms") by installing or using any of our apps or services ("Services"). + +**Minimal age**. You must be at least 13 years old to use our Services. The minimum age to use our Services without parental approval may be higher in your country. + +**Accessing the servers**. For the efficiency of the network access, the apps access all queues you create on any server via the same network (TCP/IP) connection. Our servers do not collect information about which queues were accessed via the same connection, so we do cannot establish which queues belong to the same users. Whoever might observe your network traffic would know which servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or cyphertext in common. Please refer to our [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about our privacy model and known security and privacy risks. + +**Privacy of user data**. We do not retain any data we transmit for any longer than necessary to provide the Services. We only collect aggregate statistics across all users, not per users - we do not have information about how many people use SimpleX Chat (we only know an approximate number of app installations and the aggregate traffic through our servers). In any case, we do not and will not sell or in any way monetize user data. + +**Operating our services**. For the purpose of operating our Services, you agree that your end-to-end encrypted messages are transferred via our servers in the United Kingdom, the United States and other countries where we have or use facilities and service providers or partners. + +**Software**. You agree to downloading and installing updates to our Services when they are available; they would only be automatic if you configure your devices in this way. + +**Traffic and device costs**. You are solely responsible for the traffic and device costs on which you use our Services, and any associated taxes. + +**Legal and acceptable usage**. You agree to use our Services only for legal and acceptable purposes. You will not use (or assist others in using) our Services in ways that: 1) violate or infringe the rights of SimpleX Chat, our users, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal or impermissible communications, e.g. spam. + +**Damage to SimpleX Chat**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit our Services in unauthorized manners, or in ways that harm SimpleX Chat, our Services, or systems. For example, you must not 1) access our Services or systems without authorization, other than by using the apps; 2) disrupt the integrity or performance of our Services; 3) collect information about our users in any manner; or 4) sell, rent, or charge for our Services. + +**Keeping your data secure**. SimpleX Chat is the first messaging platform that is 100% private by design - we neither have ability to access your messages, nor we have information about who you communicate with. That means that you are solely responsible for keeping your device and your user profile safe and secure. If you lose your phone or remove the app, you will not be able to recover the lost data, unless you made a back up. + +**Storing the messages on the device**. Currently the messages are stored in the database on the device without encryption - this will be added in the future. It means that if you make a backup of the app and store it unecrypted, the backup provider may be able to access the messages. + +**No Access to Emergency Services**. Our Services do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service. + +**Third-party services**. Our Services may allow you to access, use, or interact with third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services. + +**Your Rights**. You own the mesasges and information you transmit through our Services. Your recipients are able to retain the messages you receive from you; there is no technical ability to delete data from their devices. + +**License**. SimpleX Chat grants you a limited, revocable, non-exclusive, and non-transferable license to use our Services in accordance with these Terms. The source-code of services is available and can be used under [AGPL v3 licence](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE) + +**SimpleX Chat Rights**. We own all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with our Services. You may not use our copyrights, trademarks, domains, logos, and other intellectual property rights unless you have our written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat. + +**Disclaimers**. YOU USE OUR SERVICES AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. WE PROVIDE OUR SERVICES ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY US IS ACCURATE, COMPLETE, OR USEFUL, THAT OUR SERVICES WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT OUR SERVICES WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. WE DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN OUR USERS USE OUR SERVICES. WE ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF OUR USERS OR OTHER THIRD PARTIES. YOU RELEASE US, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES. + +**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR TERMS, US, OR OUR SERVICES, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. OUR AGGREGATE LIABILITY RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR TERMS, US, OR OUR SERVICES WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN OUR TERMS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW. + +**Availability**. Our Services may be interrupted, including for maintenance, upgrades, or network or equipment failures. We may discontinue some or all of our Services, including certain features and the support for certain devices and platforms, at any time. + +**Resolving disputes**. You agree to resolve any Claim you have with us relating to or arising from our Terms, us, or our Services in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern our Terms, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat and you, without regard to conflict of law provisions. + +**Changes to the terms**. SimpleX Chat may update the Terms from time to time. Your continued use of our Services confirms your acceptance of our updated Terms and supersedes any prior Terms. You will comply with all applicable export control and trade sanctions laws. Our Terms cover the entire agreement between you and SimpleX Chat regarding our Services. If you do not agree with our Terms, you should stop using our Services. + +**Enforcing the terms**. If we fail to enforce any of our Terms, that does not mean we waive the right to enforce them. If any provision of the Terms is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from our Terms and shall not affect the enforceability of the remaining provisions. Our Services are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject us to any regulations in another country. We reserve the right to limit our Services in any country. If you have specific questions about these Terms, please contact us at chat@simplex.chat. + +**Ending these Terms**. You may end these Terms with SimpleX Chat at any time by deleting SimpleX Chat app(s) from your device and discontinuing use of our Services. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the terms, Enforcing the terms, and Ending these Terms will survive termination of your relationship with SimpleX Chat. + +Updated February 28, 2022 diff --git a/apps/ios/Shared/Views/WelcomeView.swift b/apps/ios/Shared/Views/WelcomeView.swift index 65e356f31f..ddeb67afb6 100644 --- a/apps/ios/Shared/Views/WelcomeView.swift +++ b/apps/ios/Shared/Views/WelcomeView.swift @@ -49,6 +49,7 @@ struct WelcomeView: View { .textInputAutocapitalization(.never) .disableAutocorrection(true) .padding(.leading, 28) + .padding(.bottom, 2) } .padding(.bottom) TextField("Full name (optional)", text: $fullName) @@ -68,7 +69,7 @@ struct WelcomeView: View { fatalError("Failed to create user: \(error)") } } - .disabled(!validDisplayName(displayName)) + .disabled(!validDisplayName(displayName) || displayName == "") } } .padding() diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3e63cf705c..7ae1bda6af 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 16; + CURRENT_PROJECT_VERSION = 18; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -836,7 +836,7 @@ LIBRARY_SEARCH_PATHS = ""; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; - MARKETING_VERSION = 0.4; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -856,7 +856,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 16; + CURRENT_PROJECT_VERSION = 18; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -876,7 +876,7 @@ ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; - MARKETING_VERSION = 0.4; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; From 42faa2e75b6f6e5685a90fe8f4e7476865457714 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 1 Mar 2022 07:01:52 +0000 Subject: [PATCH 11/27] patch getentropy (#388) * bump nixpkgs * patch entropy * bump haskell.nix * remove file Co-authored-by: Moritz Angermann --- entropy.patch | 30 ++++++++++++++++++++ flake.lock | 78 +++++++++++++++++++++++++-------------------------- flake.nix | 5 ++-- 3 files changed, 72 insertions(+), 41 deletions(-) create mode 100644 entropy.patch diff --git a/entropy.patch b/entropy.patch new file mode 100644 index 0000000000..2add42acb3 --- /dev/null +++ b/entropy.patch @@ -0,0 +1,30 @@ +diff --git a/cbits/random_initialized.c b/cbits/random_initialized.c +index 36ac968..ab708b0 100644 +--- a/cbits/random_initialized.c ++++ b/cbits/random_initialized.c +@@ -5,14 +5,6 @@ + #include + #include + +-#ifdef HAVE_GETENTROPY +-static int ensure_pool_initialized_getentropy() +-{ +- char tmp; +- return getentropy(&tmp, sizeof(tmp)); +-} +-#endif +- + // Poll /dev/random to wait for randomness. This is a proxy for the /dev/urandom + // pool being initialized. + static int ensure_pool_initialized_poll() +@@ -45,10 +37,5 @@ static int ensure_pool_initialized_poll() + // Returns 0 on success, non-zero on failure. + int ensure_pool_initialized() + { +-#ifdef HAVE_GETENTROPY +- if (ensure_pool_initialized_getentropy() == 0) +- return 0; +-#endif +- + return ensure_pool_initialized_poll(); + } diff --git a/flake.lock b/flake.lock index 8ab1093a0c..a45f8b8272 100644 --- a/flake.lock +++ b/flake.lock @@ -20,10 +20,10 @@ "flake": false, "locked": { "lastModified": 1603716527, - "narHash": "sha256-sDbrmur9Zfp4mPKohCD8IDZfXJ0Tjxpmr2R+kg5PpSY=", + "narHash": "sha256-X0TFfdD4KZpwl0Zr6x+PLxUt/VyKQfX7ylXHdmZIL+w=", "owner": "haskell", "repo": "cabal", - "rev": "94aaa8e4720081f9c75497e2735b90f6a819b08e", + "rev": "48bf10787e27364730dd37a42b603cee8d6af7ee", "type": "github" }, "original": { @@ -36,11 +36,11 @@ "cabal-34": { "flake": false, "locked": { - "lastModified": 1622475795, - "narHash": "sha256-chwTL304Cav+7p38d9mcb+egABWmxo2Aq+xgVBgEb/U=", + "lastModified": 1640353650, + "narHash": "sha256-N1t6M3/wqj90AEdRkeC8i923gQYUpzSr8b40qVOZ1Rk=", "owner": "haskell", "repo": "cabal", - "rev": "b086c1995cdd616fc8d91f46a21e905cc50a1049", + "rev": "942639c18c0cd8ec53e0a6f8d120091af35312cd", "type": "github" }, "original": { @@ -53,11 +53,11 @@ "cabal-36": { "flake": false, "locked": { - "lastModified": 1640163203, - "narHash": "sha256-TwDWP2CffT0j40W6zr0J1Qbu+oh3nsF1lUx9446qxZM=", + "lastModified": 1641652457, + "narHash": "sha256-BlFPKP4C4HRUJeAbdembX1Rms1LD380q9s0qVDeoAak=", "owner": "haskell", "repo": "cabal", - "rev": "ecf418050c1821f25e2e218f1be94c31e0465df1", + "rev": "f27667f8ec360c475027dcaee0138c937477b070", "type": "github" }, "original": { @@ -100,11 +100,11 @@ }, "flake-utils_2": { "locked": { - "lastModified": 1623875721, - "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=", + "lastModified": 1644229661, + "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", "owner": "numtide", "repo": "flake-utils", - "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772", + "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", "type": "github" }, "original": { @@ -133,11 +133,11 @@ "hackage": { "flake": false, "locked": { - "lastModified": 1642986764, - "narHash": "sha256-U6FPiNjz9JctwKC838LEoT/xjGfb8L18ZGIEY5YYzdU=", + "lastModified": 1646097829, + "narHash": "sha256-PcHDDV8NuUxZhPV/p++IkZC+SDZ1Db7m7K+9HN4/0S4=", "owner": "input-output-hk", "repo": "hackage.nix", - "rev": "22406c79a506164c4e835a68e54739f63f918784", + "rev": "283f096976b48e54183905e7bdde7f213c6ee5cd", "type": "github" }, "original": { @@ -169,16 +169,16 @@ "stackage": "stackage" }, "locked": { - "lastModified": 1643019329, - "narHash": "sha256-So77czYvvD0jt4GJeypkqw3VNn20ype5tHnHri2s5lg=", + "lastModified": 1646102369, + "narHash": "sha256-eorqNxNiAbOMqm+juJNiqJ+zABP+JpHEh2f7rbf2Ss0=", "owner": "input-output-hk", "repo": "haskell.nix", - "rev": "ddc654e2e7e44617bfc17a5aed2a0947d3e192cc", + "rev": "d8cbc9af7750bb751255d90bc17d54e0afba3d63", "type": "github" }, "original": { "owner": "input-output-hk", - "ref": "angerman/android-static", + "ref": "angerman/try-no-libcharset", "repo": "haskell.nix", "type": "github" } @@ -202,11 +202,11 @@ "nix-tools": { "flake": false, "locked": { - "lastModified": 1636018067, - "narHash": "sha256-ng306fkuwr6V/malWtt3979iAC4yMVDDH2ViwYB6sQE=", + "lastModified": 1644395812, + "narHash": "sha256-BVFk/BEsTLq5MMZvdy3ZYHKfaS3dHrsKh4+tb5t5b58=", "owner": "input-output-hk", "repo": "nix-tools", - "rev": "ed5bd7215292deba55d6ab7a4e8c21f8b1564dda", + "rev": "d847c63b99bbec78bf83be2a61dc9f09b8a9ccc1", "type": "github" }, "original": { @@ -217,16 +217,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1641457028, - "narHash": "sha256-bA31xSpdSIo+rJMbHPurlxIsP/b6bbN+jvXOqyn2lR8=", - "owner": "angerman", + "lastModified": 1645623357, + "narHash": "sha256-vAaI91QFn/kY/uMiebW+kG2mPmxirMSJWYtkqkBKdDc=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "7b049e87e9b371f9ea6648aa8f1f2d17b2e31ae5", + "rev": "9222ae36b208d1c6b55d88e10aa68f969b5b5244", "type": "github" }, "original": { - "owner": "angerman", - "ref": "patch-1", + "owner": "nixos", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } @@ -249,11 +249,11 @@ }, "nixpkgs-2105": { "locked": { - "lastModified": 1640283157, - "narHash": "sha256-6Ddfop+rKE+Gl9Tjp9YIrkfoYPzb8F80ergdjcq3/MY=", + "lastModified": 1642244250, + "narHash": "sha256-vWpUEqQdVP4srj+/YLJRTN9vjpTs4je0cdWKXPbDItc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dde1557825c5644c869c5efc7448dc03722a8f09", + "rev": "0fd9ee1aa36ce865ad273f4f07fdc093adeb5c00", "type": "github" }, "original": { @@ -265,11 +265,11 @@ }, "nixpkgs-2111": { "locked": { - "lastModified": 1640283207, - "narHash": "sha256-SCwl7ZnCfMDsuSYvwIroiAlk7n33bW8HFfY8NvKhcPA=", + "lastModified": 1644510859, + "narHash": "sha256-xjpVvL5ecbyi0vxtVl/Fh9bwGlMbw3S06zE5nUzFB8A=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "64c7e3388bbd9206e437713351e814366e0c3284", + "rev": "0d1d5d7e3679fec9d07f2eb804d9f9fdb98378d3", "type": "github" }, "original": { @@ -281,11 +281,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1641285291, - "narHash": "sha256-KYaOBNGar3XWTxTsYPr9P6u74KAqNq0wobEC236U+0c=", + "lastModified": 1644486793, + "narHash": "sha256-EeijR4guVHgVv+JpOX3cQO+1XdrkJfGmiJ9XVsVU530=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "0432195a4b8d68faaa7d3d4b355260a3120aeeae", + "rev": "1882c6b7368fd284ad01b0a5b5601ef136321292", "type": "github" }, "original": { @@ -322,11 +322,11 @@ "stackage": { "flake": false, "locked": { - "lastModified": 1642986888, - "narHash": "sha256-oxG7LzlJdjKTJgSv7diKWsGTETDZMPT2mNNLbrBfiVs=", + "lastModified": 1646010978, + "narHash": "sha256-NpioQiTXyYm+Gm111kcDEE/ghflmnTNwPhWff54GYA4=", "owner": "input-output-hk", "repo": "stackage.nix", - "rev": "aeaf5fe21874f01702f394d01e18f472be6e3e08", + "rev": "9cce3e0d420f6c38cdbbe1c5e5bbc07fd2adfc3a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b3b7949334..ecd71e5f95 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "nix flake for simplex-chat"; - inputs.nixpkgs.url = "github:angerman/nixpkgs/patch-1"; # based on 21.11, still need this, until everything is merged into 21.11. - inputs.haskellNix.url = "github:input-output-hk/haskell.nix?ref=angerman/android-static"; + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; #angerman/nixpkgs/patch-1"; # based on 21.11, still need this, until everything is merged into 21.11. + inputs.haskellNix.url = "github:input-output-hk/haskell.nix?ref=angerman/try-no-libcharset"; inputs.haskellNix.inputs.nixpkgs.follows = "nixpkgs"; inputs.flake-utils.url = "github:numtide/flake-utils"; outputs = { self, haskellNix, nixpkgs, flake-utils }: @@ -21,6 +21,7 @@ sha256map = import ./sha256map.nix; modules = [{ packages.direct-sqlite.patches = [ ./direct-sqlite-2.3.26.patch ]; + packages.entropy.patches = [ ./entropy.patch ]; } ({ pkgs,lib, ... }: lib.mkIf (pkgs.stdenv.hostPlatform.isAndroid) { packages.simplex-chat.components.library.ghcOptions = [ "-pie" ]; From c500616bb4c229a8ed2b63f9c3a57a53ed091fe9 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 1 Mar 2022 08:45:54 +0000 Subject: [PATCH 12/27] update privacy policy, build number (#389) --- PRIVACY.md | 7 ++++--- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index c0e8dbbd56..e30a67f475 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -30,7 +30,8 @@ The cases when SimpleX Chat may need to share the data we temporarily store on t - To enforce applicable Terms, including investigation of potential violations. - To detect, prevent, or otherwise address fraud, security, or technical issues. - To protect against harm to the rights, property, or safety of SimpleX Chat, our users, or the public as required or permitted by law. -Updates + +### Updates We will update this privacy policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our Services confirms your acceptance of our updated Privacy Policy. @@ -60,7 +61,7 @@ You accept to our Terms of Service ("Terms") by installing or using any of our a **Keeping your data secure**. SimpleX Chat is the first messaging platform that is 100% private by design - we neither have ability to access your messages, nor we have information about who you communicate with. That means that you are solely responsible for keeping your device and your user profile safe and secure. If you lose your phone or remove the app, you will not be able to recover the lost data, unless you made a back up. -**Storing the messages on the device**. Currently the messages are stored in the database on the device without encryption - this will be added in the future. It means that if you make a backup of the app and store it unecrypted, the backup provider may be able to access the messages. +**Storing the messages on the device**. Currently the messages are stored in the database on your device without encryption. It means that if you make a backup of the app and store it unecrypted, the backup provider may be able to access the messages. **No Access to Emergency Services**. Our Services do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service. @@ -86,4 +87,4 @@ You accept to our Terms of Service ("Terms") by installing or using any of our a **Ending these Terms**. You may end these Terms with SimpleX Chat at any time by deleting SimpleX Chat app(s) from your device and discontinuing use of our Services. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the terms, Enforcing the terms, and Ending these Terms will survive termination of your relationship with SimpleX Chat. -Updated February 28, 2022 +Updated March 1, 2022 diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 7ae1bda6af..776ef725fc 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 18; + CURRENT_PROJECT_VERSION = 19; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -856,7 +856,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 18; + CURRENT_PROJECT_VERSION = 19; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; From a286834eb54dd4f94f375ba3c1bb904863cc1b67 Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Tue, 1 Mar 2022 12:07:15 +0000 Subject: [PATCH 13/27] bump nixpkgs --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index a45f8b8272..dc0bdac4e7 100644 --- a/flake.lock +++ b/flake.lock @@ -169,11 +169,11 @@ "stackage": "stackage" }, "locked": { - "lastModified": 1646102369, - "narHash": "sha256-eorqNxNiAbOMqm+juJNiqJ+zABP+JpHEh2f7rbf2Ss0=", + "lastModified": 1646134763, + "narHash": "sha256-/p+N9TB57Eq0lrJ7gTH2YLxHo/mZ8sT2g9oKMsAh+0M=", "owner": "input-output-hk", "repo": "haskell.nix", - "rev": "d8cbc9af7750bb751255d90bc17d54e0afba3d63", + "rev": "d5f81c2e4cd9166a5f342b3469813b56455be173", "type": "github" }, "original": { From 232149817ec47df7102bdd185b59fb2b0cce88c8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 1 Mar 2022 18:05:05 +0000 Subject: [PATCH 14/27] ios: fix alerts, build 20 (#390) --- apps/ios/Shared/ContentView.swift | 1 - apps/ios/Shared/Views/WelcomeView.swift | 13 +++---------- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index ab891a6613..3d38c4c7ec 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -30,7 +30,6 @@ struct ContentView: View { .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } } else { WelcomeView() - .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } } } diff --git a/apps/ios/Shared/Views/WelcomeView.swift b/apps/ios/Shared/Views/WelcomeView.swift index ddeb67afb6..fc57cca555 100644 --- a/apps/ios/Shared/Views/WelcomeView.swift +++ b/apps/ios/Shared/Views/WelcomeView.swift @@ -34,16 +34,9 @@ struct WelcomeView: View { .padding(.bottom) ZStack(alignment: .topLeading) { if !validDisplayName(displayName) { - Button { - AlertManager.shared.showAlertMsg( - title: "Display name", - message: "Display name can't contain spaces" - ) - } label: { - Image(systemName: "exclamationmark.circle") - .foregroundColor(.red) - .padding(.top, 4) - } + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + .padding(.top, 4) } TextField("Display name", text: $displayName) .textInputAutocapitalization(.never) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 776ef725fc..687234cfcb 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -856,7 +856,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; From 24166a4271c3d1061b34834019e75d21c786bd5d Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 1 Mar 2022 21:26:47 +0000 Subject: [PATCH 15/27] android: background loading --- .../main/java/chat/simplex/app/SimplexApp.kt | 40 +++--------------- .../java/chat/simplex/app/model/BGManager.kt | 42 +++++++++++-------- 2 files changed, 29 insertions(+), 53 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index 0afefb1700..aeb4cb0ab7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -1,13 +1,13 @@ package chat.simplex.app import android.app.Application -import android.content.Context import android.net.* import android.util.Log import androidx.lifecycle.* import androidx.work.* import chat.simplex.app.model.* import chat.simplex.app.views.helpers.withApi +import kotlinx.datetime.Clock import java.io.BufferedReader import java.io.InputStreamReader import java.util.* @@ -33,22 +33,9 @@ class SimplexApp: Application(), LifecycleEventObserver { lateinit var chatModel: ChatModel private lateinit var ntfManager: NtfManager - fun initiateBackgroundWork() { - val backgroundConstraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - val request = OneTimeWorkRequestBuilder() - .setInitialDelay(5, TimeUnit.MINUTES) - .setConstraints(backgroundConstraints) - .build() - WorkManager.getInstance(applicationContext) - .enqueue(request) - } - override fun onCreate() { super.onCreate() ProcessLifecycleOwner.get().lifecycle.addObserver(this) - registerNetworkCallback() ntfManager = NtfManager(applicationContext) val ctrl = chatInit(applicationContext.filesDir.toString()) controller = ChatController(ctrl, ntfManager, applicationContext) @@ -61,27 +48,10 @@ class SimplexApp: Application(), LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { Log.d(TAG, "onStateChanged: $event") - } - - private fun registerNetworkCallback() { - val connectivityManager = getSystemService(ConnectivityManager::class.java) - connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - Log.e(TAG, "The default network is now: " + network) - } - - override fun onLost(network: Network) { - Log.e(TAG, "The application no longer has a default network. The last default network was " + network) - } - - override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - Log.e(TAG, "The default network changed capabilities: " + networkCapabilities) - } - - override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) { - Log.e(TAG, "The default network changed link properties: " + linkProperties) - } - }) + if (event == Lifecycle.Event.ON_STOP) { + Log.e(TAG, "BGManager schedule ${Clock.System.now()}") + BGManager.schedule(applicationContext) + } } companion object { diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt index 3089a5ed0c..d274a98f6d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/BGManager.kt @@ -5,36 +5,42 @@ import android.util.Log import androidx.work.* import chat.simplex.app.TAG import chat.simplex.app.chatRecvMsg +import kotlinx.datetime.Clock +import java.time.Duration import java.util.concurrent.TimeUnit -class BackgroundAPIWorker(appContext: Context, workerParams: WorkerParameters, ctrl: ChatCtrl): +class BGManager(appContext: Context, workerParams: WorkerParameters): //, ctrl: ChatCtrl): Worker(appContext, workerParams) { - val controller = ctrl +// val controller = ctrl + + init {} + override fun doWork(): Result { - Log.d("BackgroundAPIWorker", "running") + Log.e(TAG, "BGManager doWork ${Clock.System.now()}") + schedule(applicationContext) getNewItems() - - // Enqueue another request for later to make this periodic - val request = buildRequest() - WorkManager.getInstance(applicationContext) - .enqueue(request) - return Result.success() } private fun getNewItems() { - val json = chatRecvMsg(controller) - val r = APIResponse.decodeStr(json).resp - Log.d(TAG, "chatRecvMsg: ${r.responseType}") + Log.e(TAG, "BGManager getNewItems") +// val json = chatRecvMsg(controller) +// val r = APIResponse.decodeStr(json).resp +// Log.d(TAG, "chatRecvMsg: ${r.responseType}") } - private fun buildRequest(): OneTimeWorkRequest { - val backgroundConstraints = Constraints.Builder() + companion object { + val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() - return OneTimeWorkRequestBuilder() - .setInitialDelay(5, TimeUnit.MINUTES) - .setConstraints(backgroundConstraints) - .build() + + fun schedule(appContext: Context) { + val request = OneTimeWorkRequestBuilder() + .setInitialDelay(Duration.ofMinutes(10)) + .setConstraints(constraints) + .build() + WorkManager.getInstance(appContext) + .enqueue(request) + } } } From e5d5bd5ec84f304f3d2f4425c3a5c5a46f2ea64a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 1 Mar 2022 21:39:37 +0000 Subject: [PATCH 16/27] android: disable background loading --- .../main/java/chat/simplex/app/SimplexApp.kt | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index aeb4cb0ab7..28e5fb91a4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -7,12 +7,10 @@ import androidx.lifecycle.* import androidx.work.* import chat.simplex.app.model.* import chat.simplex.app.views.helpers.withApi -import kotlinx.datetime.Clock import java.io.BufferedReader import java.io.InputStreamReader import java.util.* import java.util.concurrent.Semaphore -import java.util.concurrent.TimeUnit import kotlin.concurrent.thread const val TAG = "SIMPLEX" @@ -28,14 +26,15 @@ external fun chatInit(path: String): ChatCtrl external fun chatSendCmd(ctrl: ChatCtrl, msg: String) : String external fun chatRecvMsg(ctrl: ChatCtrl) : String -class SimplexApp: Application(), LifecycleEventObserver { +//class SimplexApp: Application(), LifecycleEventObserver { +class SimplexApp: Application() { private lateinit var controller: ChatController lateinit var chatModel: ChatModel private lateinit var ntfManager: NtfManager override fun onCreate() { super.onCreate() - ProcessLifecycleOwner.get().lifecycle.addObserver(this) +// ProcessLifecycleOwner.get().lifecycle.addObserver(this) ntfManager = NtfManager(applicationContext) val ctrl = chatInit(applicationContext.filesDir.toString()) controller = ChatController(ctrl, ntfManager, applicationContext) @@ -46,13 +45,13 @@ class SimplexApp: Application(), LifecycleEventObserver { } } - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - Log.d(TAG, "onStateChanged: $event") - if (event == Lifecycle.Event.ON_STOP) { - Log.e(TAG, "BGManager schedule ${Clock.System.now()}") - BGManager.schedule(applicationContext) - } - } +// override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { +// Log.d(TAG, "onStateChanged: $event") +// if (event == Lifecycle.Event.ON_STOP) { +// Log.e(TAG, "BGManager schedule ${Clock.System.now()}") +// BGManager.schedule(applicationContext) +// } +// } companion object { init { From eb42d739cb34c63d392e41a83772b7867d7188e5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 2 Mar 2022 07:11:20 +0000 Subject: [PATCH 17/27] android: update version/build 0.4 (6) --- apps/android/app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index f129fd1784..5695497497 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -9,10 +9,10 @@ android { defaultConfig { applicationId "chat.simplex.app" - minSdk 26 + minSdk 29 targetSdk 32 - versionCode 5 - versionName "0.3" + versionCode 6 + versionName "0.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { From 3a6eaa3ddd3afa3aacb09e75b08ec78f920d213b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 2 Mar 2022 15:57:21 +0000 Subject: [PATCH 18/27] android: update build number (7) --- apps/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 5695497497..8d6d1556a7 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -11,7 +11,7 @@ android { applicationId "chat.simplex.app" minSdk 29 targetSdk 32 - versionCode 6 + versionCode 7 versionName "0.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From 2e5b123749c1ecf04294866b3421c2a9d5faa9e3 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:04:06 +0000 Subject: [PATCH 19/27] update simplexmq --- cabal.project | 2 +- sha256map.nix | 2 +- stack.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index 0ae6c0efd7..609b8ef40b 100644 --- a/cabal.project +++ b/cabal.project @@ -3,7 +3,7 @@ packages: . source-repository-package type: git location: git://github.com/simplex-chat/simplexmq.git - tag: d1e6147adfbd46f5e3e996cc6365d8f3f0f7669c + tag: 7a19ab224bdd1122f0761704b6ca1eb4e1e26eb7 source-repository-package type: git diff --git a/sha256map.nix b/sha256map.nix index 8b428b97eb..6f6c57d8b4 100644 --- a/sha256map.nix +++ b/sha256map.nix @@ -1,5 +1,5 @@ { - "git://github.com/simplex-chat/simplexmq.git"."d1e6147adfbd46f5e3e996cc6365d8f3f0f7669c" = "11wny0ivhrrp36757i074ml18k6nv7hq6a5dvv4rg3npqf19y3r7"; + "git://github.com/simplex-chat/simplexmq.git"."7a19ab224bdd1122f0761704b6ca1eb4e1e26eb7" = "1sn2bzz5v2r6wxf1p2k9578zwp0vlb42lb6xjqwpl4acr47wcx0g"; "git://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp"; "git://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj"; "git://github.com/zw3rk/android-support.git"."3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb" = "1r6jyxbim3dsvrmakqfyxbd6ms6miaghpbwyl0sr6dzwpgaprz97"; diff --git a/stack.yaml b/stack.yaml index 131d1766de..221d206e58 100644 --- a/stack.yaml +++ b/stack.yaml @@ -48,7 +48,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: d1e6147adfbd46f5e3e996cc6365d8f3f0f7669c + commit: 7a19ab224bdd1122f0761704b6ca1eb4e1e26eb7 # - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977 - github: simplex-chat/aeson commit: 3eb66f9a68f103b5f1489382aad89f5712a64db7 From 9d4de4b2955112a4a28cc8b95862886508af9f54 Mon Sep 17 00:00:00 2001 From: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com> Date: Wed, 2 Mar 2022 22:18:14 +0400 Subject: [PATCH 20/27] core: correctly set "yes to migrations" in agent config (#393) --- src/Simplex/Chat/Mobile.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 8b829241ab..be007c19e3 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -20,6 +20,7 @@ import Simplex.Chat.Controller import Simplex.Chat.Options import Simplex.Chat.Store import Simplex.Chat.Types +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (yesToMigrations)) import Simplex.Messaging.Protocol (CorrId (..)) foreign export ccall "chat_init" cChatInit :: CString -> IO (StablePtr ChatController) @@ -57,7 +58,7 @@ defaultMobileConfig :: ChatConfig defaultMobileConfig = defaultChatConfig { yesToMigrations = True, - agentConfig = agentConfig defaultChatConfig {yesToMigrations = True} + agentConfig = (agentConfig defaultChatConfig) {yesToMigrations = True} } type CJSONString = CString @@ -68,7 +69,7 @@ getActiveUser_ st = find activeUser <$> getUsers st chatInit :: String -> IO ChatController chatInit dbFilePrefix = do let f = chatStoreFile dbFilePrefix - chatStore <- createStore f (dbPoolSize defaultMobileConfig) (yesToMigrations defaultMobileConfig) + chatStore <- createStore f (dbPoolSize defaultMobileConfig) (yesToMigrations (defaultMobileConfig :: ChatConfig)) user_ <- getActiveUser_ chatStore newChatController chatStore user_ defaultMobileConfig mobileChatOpts {dbFilePrefix} (const $ pure ()) From b10b3a3434d26e12069851538489dc1e26d870b7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:15:22 +0000 Subject: [PATCH 21/27] ios: update libs --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 61 +++++++++++----------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 687234cfcb..c28778fd33 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -29,16 +29,16 @@ 5C5346A927B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; 5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; }; 5C577F7E27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; }; - 5C67D2FA27CB9FA100E4261F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F527CB9FA100E4261F /* libgmp.a */; }; - 5C67D2FB27CB9FA100E4261F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F527CB9FA100E4261F /* libgmp.a */; }; - 5C67D2FC27CB9FA100E4261F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F627CB9FA100E4261F /* libgmpxx.a */; }; - 5C67D2FD27CB9FA100E4261F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F627CB9FA100E4261F /* libgmpxx.a */; }; - 5C67D2FE27CB9FA100E4261F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F727CB9FA100E4261F /* libffi.a */; }; - 5C67D2FF27CB9FA100E4261F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F727CB9FA100E4261F /* libffi.a */; }; - 5C67D30027CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */; }; - 5C67D30127CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */; }; - 5C67D30227CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */; }; - 5C67D30327CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */; }; + 5C67D31827D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31327D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a */; }; + 5C67D31927D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31327D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a */; }; + 5C67D31A27D0003A00E4261F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31427D0003A00E4261F /* libffi.a */; }; + 5C67D31B27D0003A00E4261F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31427D0003A00E4261F /* libffi.a */; }; + 5C67D31C27D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31527D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a */; }; + 5C67D31D27D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31527D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a */; }; + 5C67D31E27D0003A00E4261F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31627D0003A00E4261F /* libgmpxx.a */; }; + 5C67D31F27D0003A00E4261F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31627D0003A00E4261F /* libgmpxx.a */; }; + 5C67D32027D0003A00E4261F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31727D0003A00E4261F /* libgmp.a */; }; + 5C67D32127D0003A00E4261F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C67D31727D0003A00E4261F /* libgmp.a */; }; 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; 5C6AD81427A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; 5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */; }; @@ -135,11 +135,11 @@ 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = ""; }; 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownHelp.swift; sourceTree = ""; }; - 5C67D2F527CB9FA100E4261F /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C67D2F627CB9FA100E4261F /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5C67D2F727CB9FA100E4261F /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a"; sourceTree = ""; }; - 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a"; sourceTree = ""; }; + 5C67D31327D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a"; sourceTree = ""; }; + 5C67D31427D0003A00E4261F /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C67D31527D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a"; sourceTree = ""; }; + 5C67D31627D0003A00E4261F /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C67D31727D0003A00E4261F /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = ""; }; 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMetaView.swift; sourceTree = ""; }; 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavLinkPlain.swift; sourceTree = ""; }; @@ -186,14 +186,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C67D30227CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */, 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */, - 5C67D2FE27CB9FA100E4261F /* libffi.a in Frameworks */, - 5C67D2FC27CB9FA100E4261F /* libgmpxx.a in Frameworks */, 5C764E83279C748B000C6508 /* libz.tbd in Frameworks */, - 5C67D2FA27CB9FA100E4261F /* libgmp.a in Frameworks */, - 5C67D30027CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */, + 5C67D32027D0003A00E4261F /* libgmp.a in Frameworks */, + 5C67D31C27D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a in Frameworks */, 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */, + 5C67D31A27D0003A00E4261F /* libffi.a in Frameworks */, + 5C67D31827D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a in Frameworks */, + 5C67D31E27D0003A00E4261F /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -201,13 +201,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C67D2FD27CB9FA100E4261F /* libgmpxx.a in Frameworks */, + 5C67D31F27D0003A00E4261F /* libgmpxx.a in Frameworks */, + 5C67D32127D0003A00E4261F /* libgmp.a in Frameworks */, + 5C67D31927D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a in Frameworks */, 5C764E85279C748C000C6508 /* libz.tbd in Frameworks */, - 5C67D2FB27CB9FA100E4261F /* libgmp.a in Frameworks */, - 5C67D2FF27CB9FA100E4261F /* libffi.a in Frameworks */, + 5C67D31D27D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a in Frameworks */, 5C764E84279C748C000C6508 /* libiconv.tbd in Frameworks */, - 5C67D30127CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a in Frameworks */, - 5C67D30327CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a in Frameworks */, + 5C67D31B27D0003A00E4261F /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -259,11 +259,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C67D2F727CB9FA100E4261F /* libffi.a */, - 5C67D2F527CB9FA100E4261F /* libgmp.a */, - 5C67D2F627CB9FA100E4261F /* libgmpxx.a */, - 5C67D2F827CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM-ghc8.10.7.a */, - 5C67D2F927CB9FA100E4261F /* libHSsimplex-chat-1.3.0-7NpjDBQ3CKKBWRdMwbLLzM.a */, + 5C67D31427D0003A00E4261F /* libffi.a */, + 5C67D31727D0003A00E4261F /* libgmp.a */, + 5C67D31627D0003A00E4261F /* libgmpxx.a */, + 5C67D31327D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla-ghc8.10.7.a */, + 5C67D31527D0003A00E4261F /* libHSsimplex-chat-1.3.0-5IozqhzNoFs59GB71w8Qla.a */, ); path = Libraries; sourceTree = ""; @@ -833,7 +833,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ""; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; MARKETING_VERSION = 1.0; From c47a7d78fe37a9512fe3a5617acadbc314b48512 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 3 Mar 2022 08:32:25 +0000 Subject: [PATCH 22/27] support for unknown message content types (#395) * android: parse/serialize unknown chat items * ios: more resilient decoding of MsgContent * core: preserve JSON of unknown message content type in MCUknown, so it can be parsed once it is supported by the client --- apps/android/app/build.gradle | 1 + .../java/chat/simplex/app/model/ChatModel.kt | 61 ++++++++++++++++--- apps/ios/Shared/Model/ChatModel.swift | 7 +-- src/Simplex/Chat/Protocol.hs | 30 ++++----- src/Simplex/Chat/View.hs | 2 +- 5 files changed, 72 insertions(+), 29 deletions(-) diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 8d6d1556a7..49cc21bee9 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -45,6 +45,7 @@ android { freeCompilerArgs += "-opt-in=androidx.compose.material.ExperimentalMaterialApi" freeCompilerArgs += "-opt-in=com.google.accompanist.insets.ExperimentalAnimatedInsets" freeCompilerArgs += "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi" + freeCompilerArgs += "-opt-in=kotlinx.serialization.InternalSerializationApi" } externalNativeBuild { cmake { diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 96b08e86db..92c96064c8 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -10,8 +10,12 @@ import androidx.compose.ui.text.style.TextDecoration import chat.simplex.app.ui.theme.SecretColor import chat.simplex.app.ui.theme.SimplexBlue import kotlinx.datetime.* -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import kotlinx.serialization.* +import kotlinx.serialization.builtins.IntArraySerializer +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.SerializersModule class ChatModel(val controller: ChatController) { var currentUser = mutableStateOf(null) @@ -587,14 +591,57 @@ sealed class CIContent { } } -@Serializable +@Serializable(with = MsgContentSerializer::class) sealed class MsgContent { abstract val text: String - abstract val cmdString: String - @Serializable @SerialName("text") - class MCText(override val text: String): MsgContent() { - override val cmdString get() = "text $text" + class MCText(override val text: String): MsgContent() + class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent() + + val cmdString: String get() = when (this) { + is MCText -> "text $text" + is MCUnknown -> "json $json" + } +} + +object MsgContentSerializer : KSerializer { + override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) { + element("MCText", buildClassSerialDescriptor("MCText") { + element("text") + }) + element("MCUnknown", buildClassSerialDescriptor("MCUnknown")) + } + + override fun deserialize(decoder: Decoder): MsgContent { + require(decoder is JsonDecoder) + val json = decoder.decodeJsonElement() + return if (json is JsonObject) { + if ("type" in json) { + val t = json["type"]?.jsonPrimitive?.content ?: "" + val text = json["text"]?.jsonPrimitive?.content ?: "unknown message format" + when (t) { + "text" -> MsgContent.MCText(text) + else -> MsgContent.MCUnknown(t, text, json) + } + } else { + MsgContent.MCUnknown(text = "invalid message format", json = json) + } + } else { + MsgContent.MCUnknown(text = "invalid message format", json = json) + } + } + + override fun serialize(encoder: Encoder, value: MsgContent) { + require(encoder is JsonEncoder) + val json = when (value) { + is MsgContent.MCText -> + buildJsonObject { + put("type", "text") + put("text", value.text) + } + is MsgContent.MCUnknown -> value.json + } + encoder.encodeJsonElement(json) } } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index d3d80e070c..3361c9b710 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -625,15 +625,14 @@ struct RcvFileTransfer: Decodable { enum MsgContent { case text(String) + // TODO include original JSON, possibly using https://github.com/zoul/generic-json-swift case unknown(type: String, text: String) - case invalid(error: String) var text: String { get { switch self { case let .text(text): return text case let .unknown(_, text): return text - case .invalid: return "invalid" } } } @@ -655,8 +654,8 @@ enum MsgContent { extension MsgContent: Decodable { init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) do { + let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(String.self, forKey: CodingKeys.type) switch type { case "text": @@ -667,7 +666,7 @@ extension MsgContent: Decodable { self = .unknown(type: type, text: text ?? "unknown message format") } } catch { - self = .invalid(error: String(describing: error)) + self = .unknown(type: "unknown", text: "invalid message format") } } } diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index c1d35e3079..ace8eeb06c 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -15,10 +15,12 @@ module Simplex.Chat.Protocol where import Control.Monad ((<=<)) import Data.Aeson (FromJSON, ToJSON, (.:), (.:?), (.=)) import qualified Data.Aeson as J +import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.KeyMap as JM import qualified Data.Aeson.Types as JT import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString.Lazy.Char8 as LB +import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Database.SQLite.Simple.FromField (FromField (..)) @@ -107,26 +109,24 @@ instance ToJSON MsgContentType where toJSON = strToJSON toEncoding = strToJEncoding --- TODO - include tag and original JSON into MCUnknown so that information is not lost --- so when it serializes back it is the same as it was and chat upgrade makes it readable -data MsgContent = MCText Text | MCUnknown +data MsgContent = MCText Text | MCUnknown J.Value Text deriving (Eq, Show) msgContentText :: MsgContent -> Text msgContentText = \case MCText t -> t - MCUnknown -> unknownMsgType + MCUnknown _ t -> t toMsgContentType :: MsgContent -> MsgContentType toMsgContentType = \case MCText _ -> MCText_ - MCUnknown -> MCUnknown_ + MCUnknown {} -> MCUnknown_ instance FromJSON MsgContent where - parseJSON (J.Object v) = do + parseJSON jv@(J.Object v) = do v .: "type" >>= \case MCText_ -> MCText <$> v .: "text" - MCUnknown_ -> pure MCUnknown + MCUnknown_ -> MCUnknown jv . fromMaybe unknownMsgType <$> v .:? "text" parseJSON invalid = JT.prependFailure "bad MsgContent, " (JT.typeMismatch "Object" invalid) @@ -134,16 +134,12 @@ unknownMsgType :: Text unknownMsgType = "unknown message type" instance ToJSON MsgContent where - toJSON mc = - J.object $ - ("type" .= toMsgContentType mc) : case mc of - MCText t -> ["text" .= t] - MCUnknown -> ["text" .= unknownMsgType] - toEncoding mc = - J.pairs $ - ("type" .= toMsgContentType mc) <> case mc of - MCText t -> "text" .= t - MCUnknown -> "text" .= unknownMsgType + toJSON = \case + MCUnknown v _ -> v + MCText t -> J.object ["type" .= MCText_, "text" .= t] + toEncoding = \case + MCUnknown v _ -> JE.value v + MCText t -> J.pairs $ "type" .= MCText_ <> "text" .= t data CMEventTag = XMsgNew_ diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 08c2842af3..ecfb033a6e 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -371,7 +371,7 @@ ttyMsgTime = styleTime . formatTime defaultTimeLocale "%H:%M" ttyMsgContent :: MsgContent -> [StyledString] ttyMsgContent = \case MCText t -> msgPlain t - MCUnknown -> ["unknown message type"] + MCUnknown _ t -> msgPlain t ttySentFile :: StyledString -> FileTransferId -> FilePath -> [StyledString] ttySentFile to fId fPath = ["/f " <> to <> ttyFilePath fPath, "use " <> highlight ("/fc " <> show fId) <> " to cancel sending"] From 87e9ae5a3e1d437f78281d06701609613bea8583 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:30:47 +0000 Subject: [PATCH 23/27] mobile: update verion/build: ios 1.0/21, android 0.4.1/8 --- apps/android/app/build.gradle | 4 ++-- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 49cc21bee9..34b4cf6ab2 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "chat.simplex.app" minSdk 29 targetSdk 32 - versionCode 7 - versionName "0.4" + versionCode 8 + versionName "0.4.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index c28778fd33..73c1888d6e 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -815,7 +815,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -855,7 +855,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; From 1ab68172cb47b9eb661fde37482431a8925a2e4c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:34:39 +0000 Subject: [PATCH 24/27] ios: update version 1.1 (build 21) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 73c1888d6e..0fedf61c19 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -835,7 +835,7 @@ ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -875,7 +875,7 @@ ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; From fd28c939f57dfe9f93c6fb930ebc056154f88602 Mon Sep 17 00:00:00 2001 From: IanRDavies Date: Fri, 4 Mar 2022 14:51:25 +0000 Subject: [PATCH 25/27] android: disable create button when display name is not valid (#396) --- .../app/src/main/java/chat/simplex/app/views/WelcomeView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt index bacd1dad29..721553f2c0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt @@ -153,7 +153,7 @@ fun CreateProfilePanel(chatModel: ChatModel) { chatModel.controller.startChat(user) } }, - enabled = displayName.isNotEmpty() + enabled = (displayName.isNotEmpty() && isValidDisplayName(displayName)) ) { Text("Create") } } } From 7cf139f856323474afd6dd4b18b7cfe352229fb9 Mon Sep 17 00:00:00 2001 From: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com> Date: Sat, 5 Mar 2022 12:34:48 +0400 Subject: [PATCH 26/27] prepare v1.3.1 (#398) --- package.yaml | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Controller.hs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.yaml b/package.yaml index 111b557f0d..1d1ee367ec 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 1.3.0 +version: 1.3.1 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 053d771f31..ee2cf0b4b4 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 1.3.0 +version: 1.3.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 079d32fb3d..6b44a6e477 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -36,7 +36,7 @@ import System.IO (Handle) import UnliftIO.STM versionNumber :: String -versionNumber = "1.3.0" +versionNumber = "1.3.1" versionStr :: String versionStr = "SimpleX Chat v" <> versionNumber From 33f731e247f9f2b1c95c088428a879d4bfc6d0c6 Mon Sep 17 00:00:00 2001 From: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com> Date: Sat, 5 Mar 2022 13:01:16 +0400 Subject: [PATCH 27/27] 1.3.1