From 4d18174b11b5ff09aef43925c55a74be4b9a54af Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 24 Aug 2024 19:10:30 +0100 Subject: [PATCH 01/12] ui: fix Debug delivery (#4757) --- apps/ios/Shared/Model/SimpleXAPI.swift | 4 ++-- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 11 ++++++++++- .../chat/simplex/common/model/SimpleXAPI.kt | 16 +++++++++++++--- .../simplex/common/views/chat/ChatInfoView.kt | 2 +- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ebc58c6a05..239ef7916e 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -582,13 +582,13 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (Gro throw r } -func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { +func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } throw r } -func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { +func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } throw r diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index ea3b04c2ff..35adcd49c1 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -942,7 +942,7 @@ func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Al ) } -func queueInfoText(_ info: (RcvMsgInfo?, QueueInfo)) -> String { +func queueInfoText(_ info: (RcvMsgInfo?, ServerQueueInfo)) -> String { let (rcvMsgInfo, qInfo) = info var msgInfo: String if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a6409dec2f..a5a475de11 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -537,7 +537,7 @@ public enum ChatResponse: Decodable, Error { case networkConfig(networkConfig: NetCfg) case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) - case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: QueueInfo) + case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: ServerQueueInfo) case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) @@ -2294,6 +2294,15 @@ public struct RcvMsgInfo: Codable, Hashable { var agentMsgMeta: String } +public struct ServerQueueInfo: Codable, Hashable { + var server: String + var rcvId: String + var sndId: String + var ntfId: String? + var status: String + var info: QueueInfo +} + public struct QueueInfo: Codable, Hashable { var qiSnd: Bool var qiNtf: Bool diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index fe568b5144..983390b09e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1030,14 +1030,14 @@ object ChatController { return null } - suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { + suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { val r = sendCmd(rh, CC.APIContactQueueInfo(contactId)) if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) return null } - suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) @@ -4734,7 +4734,7 @@ sealed class CR { @Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR() @Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR() @Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats? = null): CR() - @Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: QueueInfo): CR() + @Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: ServerQueueInfo): CR() @Serializable @SerialName("contactSwitchStarted") class ContactSwitchStarted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("groupMemberSwitchStarted") class GroupMemberSwitchStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("contactSwitchAborted") class ContactSwitchAborted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @@ -6409,6 +6409,16 @@ data class RcvMsgInfo ( val agentMsgMeta: String ) +@Serializable +data class ServerQueueInfo ( + val server: String, + val rcvId: String, + val sndId: String, + val ntfId: String? = null, + val status: String, + val info: QueueInfo +) + @Serializable data class QueueInfo ( val qiSnd: Boolean, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 24416ff49e..9149b039ef 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -1268,7 +1268,7 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) { ) } -fun queueInfoText(info: Pair): String { +fun queueInfoText(info: Pair): String { val (rcvMsgInfo, qInfo) = info val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none) return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo) From 4552860345feef1dbdbde2c4e9fd072567872ed3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 25 Aug 2024 14:31:26 +0100 Subject: [PATCH 02/12] ios: remove unnecessary protocols (#4763) --- apps/ios/SimpleXChat/APITypes.swift | 78 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a5a475de11..d7fc533e91 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1094,12 +1094,12 @@ public enum GroupLinkPlan: Decodable, Hashable { case known(groupInfo: GroupInfo) } -struct NewUser: Encodable, Hashable { +struct NewUser: Encodable { var profile: Profile? var pastTimestamp: Bool } -public enum ChatPagination: Hashable { +public enum ChatPagination { case last(count: Int) case after(chatItemId: Int64, count: Int) case before(chatItemId: Int64, count: Int) @@ -1315,7 +1315,7 @@ public struct ServerAddress: Decodable { ) } -public struct NetCfg: Codable, Equatable, Hashable { +public struct NetCfg: Codable, Equatable { public var socksProxy: String? = nil var socksMode: SocksMode = .always public var hostMode: HostMode = .publicHost @@ -1369,18 +1369,18 @@ public struct NetCfg: Codable, Equatable, Hashable { public var enableKeepAlive: Bool { tcpKeepAlive != nil } } -public enum HostMode: String, Codable, Hashable { +public enum HostMode: String, Codable { case onionViaSocks case onionHost = "onion" case publicHost = "public" } -public enum SocksMode: String, Codable, Hashable { +public enum SocksMode: String, Codable { case always = "always" case onion = "onion" } -public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { +public enum SMPProxyMode: String, Codable, SelectableItem { case always = "always" case unknown = "unknown" case unprotected = "unprotected" @@ -1400,7 +1400,7 @@ public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never] } -public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { +public enum SMPProxyFallback: String, Codable, SelectableItem { case allow = "allow" case allowProtected = "allowProtected" case prohibit = "prohibit" @@ -1418,7 +1418,7 @@ public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit] } -public enum OnionHosts: String, Identifiable, Hashable { +public enum OnionHosts: String, Identifiable { case no case prefer case require @@ -1452,7 +1452,7 @@ public enum OnionHosts: String, Identifiable, Hashable { public static let values: [OnionHosts] = [.no, .prefer, .require] } -public enum TransportSessionMode: String, Codable, Identifiable, Hashable { +public enum TransportSessionMode: String, Codable, Identifiable { case user case entity @@ -1468,7 +1468,7 @@ public enum TransportSessionMode: String, Codable, Identifiable, Hashable { public static let values: [TransportSessionMode] = [.user, .entity] } -public struct KeepAliveOpts: Codable, Equatable, Hashable { +public struct KeepAliveOpts: Codable, Equatable { public var keepIdle: Int // seconds public var keepIntvl: Int // seconds public var keepCnt: Int // times @@ -1476,7 +1476,7 @@ public struct KeepAliveOpts: Codable, Equatable, Hashable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } -public enum NetworkStatus: Decodable, Equatable, Hashable { +public enum NetworkStatus: Decodable, Equatable { case unknown case connected case disconnected @@ -1514,7 +1514,7 @@ public enum NetworkStatus: Decodable, Equatable, Hashable { } } -public struct ConnNetworkStatus: Decodable, Hashable { +public struct ConnNetworkStatus: Decodable { public var agentConnId: String public var networkStatus: NetworkStatus } @@ -1539,7 +1539,7 @@ public enum MsgFilter: String, Codable, Hashable { case mentions } -public struct UserMsgReceiptSettings: Codable, Hashable { +public struct UserMsgReceiptSettings: Codable { public var enable: Bool public var clearOverrides: Bool @@ -1588,7 +1588,7 @@ public enum SndSwitchStatus: String, Codable, Hashable { case sendingQTEST = "sending_qtest" } -public enum QueueDirection: String, Decodable, Hashable { +public enum QueueDirection: String, Decodable { case rcv case snd } @@ -1643,12 +1643,12 @@ public struct AutoAccept: Codable, Hashable { } } -public protocol SelectableItem: Hashable, Identifiable { +public protocol SelectableItem: Identifiable, Equatable { var label: LocalizedStringKey { get } static var values: [Self] { get } } -public struct DeviceToken: Decodable, Hashable { +public struct DeviceToken: Decodable { var pushProvider: PushProvider var token: String @@ -1662,12 +1662,12 @@ public struct DeviceToken: Decodable, Hashable { } } -public enum PushEnvironment: String, Hashable { +public enum PushEnvironment: String { case development case production } -public enum PushProvider: String, Decodable, Hashable { +public enum PushProvider: String, Decodable { case apns_dev case apns_prod @@ -1681,7 +1681,7 @@ public enum PushProvider: String, Decodable, Hashable { // This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, // and .local for periodic background checks -public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { +public enum NotificationsMode: String, Decodable, SelectableItem { case off = "OFF" case periodic = "PERIODIC" case instant = "INSTANT" @@ -1699,7 +1699,7 @@ public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { public static var values: [NotificationsMode] = [.instant, .periodic, .off] } -public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { +public enum NotificationPreviewMode: String, SelectableItem, Codable { case hidden case contact case message @@ -1717,7 +1717,7 @@ public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden] } -public struct RemoteCtrlInfo: Decodable, Hashable { +public struct RemoteCtrlInfo: Decodable { public var remoteCtrlId: Int64 public var ctrlDeviceName: String public var sessionState: RemoteCtrlSessionState? @@ -1727,7 +1727,7 @@ public struct RemoteCtrlInfo: Decodable, Hashable { } } -public enum RemoteCtrlSessionState: Decodable, Hashable { +public enum RemoteCtrlSessionState: Decodable { case starting case searching case connecting @@ -1742,17 +1742,17 @@ public enum RemoteCtrlStopReason: Decodable { case disconnected } -public struct CtrlAppInfo: Decodable, Hashable { +public struct CtrlAppInfo: Decodable { public var appVersionRange: AppVersionRange public var deviceName: String } -public struct AppVersionRange: Decodable, Hashable { +public struct AppVersionRange: Decodable { public var minVersion: String public var maxVersion: String } -public struct CoreVersionInfo: Decodable, Hashable { +public struct CoreVersionInfo: Decodable { public var version: String public var simplexmqVersion: String public var simplexmqCommit: String @@ -2090,14 +2090,14 @@ public enum RemoteCtrlError: Decodable, Hashable { case protocolError } -public struct MigrationFileLinkData: Codable, Hashable { +public struct MigrationFileLinkData: Codable { let networkConfig: NetworkConfig? public init(networkConfig: NetworkConfig) { self.networkConfig = networkConfig } - public struct NetworkConfig: Codable, Hashable { + public struct NetworkConfig: Codable { let socksProxy: String? let hostMode: HostMode? let requiredHostMode: Bool? @@ -2129,7 +2129,7 @@ public struct MigrationFileLinkData: Codable, Hashable { } } -public struct AppSettings: Codable, Equatable, Hashable { +public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil public var privacyEncryptLocalFiles: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil @@ -2224,7 +2224,7 @@ public struct AppSettings: Codable, Equatable, Hashable { } } -public enum AppSettingsNotificationMode: String, Codable, Hashable { +public enum AppSettingsNotificationMode: String, Codable { case off case periodic case instant @@ -2252,13 +2252,13 @@ public enum AppSettingsNotificationMode: String, Codable, Hashable { // case message //} -public enum AppSettingsLockScreenCalls: String, Codable, Hashable { +public enum AppSettingsLockScreenCalls: String, Codable { case disable case show case accept } -public struct UserNetworkInfo: Codable, Equatable, Hashable { +public struct UserNetworkInfo: Codable, Equatable { public let networkType: UserNetworkType public let online: Bool @@ -2268,7 +2268,7 @@ public struct UserNetworkInfo: Codable, Equatable, Hashable { } } -public enum UserNetworkType: String, Codable, Hashable { +public enum UserNetworkType: String, Codable { case none case cellular case wifi @@ -2286,7 +2286,7 @@ public enum UserNetworkType: String, Codable, Hashable { } } -public struct RcvMsgInfo: Codable, Hashable { +public struct RcvMsgInfo: Codable { var msgId: Int64 var msgDeliveryId: Int64 var msgDeliveryStatus: String @@ -2294,7 +2294,7 @@ public struct RcvMsgInfo: Codable, Hashable { var agentMsgMeta: String } -public struct ServerQueueInfo: Codable, Hashable { +public struct ServerQueueInfo: Codable { var server: String var rcvId: String var sndId: String @@ -2303,7 +2303,7 @@ public struct ServerQueueInfo: Codable, Hashable { var info: QueueInfo } -public struct QueueInfo: Codable, Hashable { +public struct QueueInfo: Codable { var qiSnd: Bool var qiNtf: Bool var qiSub: QSub? @@ -2311,25 +2311,25 @@ public struct QueueInfo: Codable, Hashable { var qiMsg: MsgInfo? } -public struct QSub: Codable, Hashable { +public struct QSub: Codable { var qSubThread: QSubThread var qDelivered: String? } -public enum QSubThread: String, Codable, Hashable { +public enum QSubThread: String, Codable { case noSub case subPending case subThread case prohibitSub } -public struct MsgInfo: Codable, Hashable { +public struct MsgInfo: Codable { var msgId: String var msgTs: Date var msgType: MsgType } -public enum MsgType: String, Codable, Hashable { +public enum MsgType: String, Codable { case message case quota } From 0118e64ab497b4874918f3cf494018f3a0d1acce Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:59:57 +0000 Subject: [PATCH 03/12] android, desktop: items padding and min height (#4767) --- .../views/usersettings/Appearance.android.kt | 7 ++----- .../kotlin/chat/simplex/common/model/ChatModel.kt | 7 +++++++ .../simplex/common/views/chat/ChatItemInfoView.kt | 10 +++++----- .../common/views/chat/group/GroupChatInfoView.kt | 4 ++-- .../common/views/chatlist/ShareListNavLinkView.kt | 8 +++++--- .../common/views/chatlist/ShareListView.kt | 15 +++++++++++---- .../common/views/database/DatabaseErrorView.kt | 4 +--- .../simplex/common/views/helpers/CloseSheetBar.kt | 3 ++- .../chat/simplex/common/views/helpers/Section.kt | 6 +++--- .../simplex/common/views/newchat/NewChatView.kt | 8 ++++---- .../common/views/remote/ConnectDesktopView.kt | 12 +++++------- .../common/views/usersettings/Appearance.kt | 4 ++-- .../views/usersettings/HiddenProfileView.kt | 5 +---- .../views/usersettings/NetworkAndServers.kt | 3 +-- .../common/views/usersettings/UserProfilesView.kt | 3 +-- .../views/usersettings/Appearance.desktop.kt | 5 ++--- 16 files changed, 54 insertions(+), 50 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index b985601962..6a76be0fe4 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -14,13 +14,11 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -33,7 +31,6 @@ import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.saveAppLocale -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.painterResource @@ -82,7 +79,7 @@ fun AppearanceScope.AppearanceLayout( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.appearance_settings)) - SectionView(stringResource(MR.strings.settings_section_title_interface), padding = PaddingValues()) { + SectionView(stringResource(MR.strings.settings_section_title_interface), contentPadding = PaddingValues()) { val context = LocalContext.current // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // SectionItemWithValue( @@ -123,7 +120,7 @@ fun AppearanceScope.AppearanceLayout( SectionDividerSpaced(maxTopPadding = true) - SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { + SectionView(stringResource(MR.strings.settings_section_title_icon), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { LazyRow { items(AppIcon.values().size, { index -> AppIcon.values()[index] }) { index -> val item = AppIcon.values()[index] diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 628481477d..e92b3d714a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -985,6 +985,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val fullName get() = contact.fullName override val image get() = contact.image override val localAlias: String get() = contact.localAlias + override fun anyNameContains(searchAnyCase: String): Boolean = contact.anyNameContains(searchAnyCase) companion object { val sampleData = Direct(Contact.sampleData) @@ -1219,6 +1220,12 @@ data class Contact( override val localAlias get() = profile.localAlias val verified get() = activeConn?.connectionCode != null + override fun anyNameContains(searchAnyCase: String): Boolean { + val s = searchAnyCase.trim().lowercase() + return profile.chatViewName.lowercase().contains(s) || profile.displayName.lowercase().contains(s) || profile.fullName.lowercase().contains(s) + } + + val directOrUsed: Boolean get() = if (activeConn != null) { (activeConn.connLevel == 0 && !activeConn.viaGroupLink) || contactUsed diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index d0e972965a..c403fe512b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -282,14 +282,14 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) val versions = ciInfo.itemVersions if (versions.isNotEmpty()) { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(stringResource(MR.strings.edit_history), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) versions.forEachIndexed { i, ciVersion -> ItemVersionView(ciVersion, current = i == 0) } } } else { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(stringResource(MR.strings.no_history), color = MaterialTheme.colors.secondary) } @@ -304,7 +304,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(stringResource(MR.strings.in_reply_to), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) QuotedMsgView(qi) } @@ -381,14 +381,14 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) val mss = membersStatuses(chatModel, memberDeliveryStatuses) if (mss.isNotEmpty()) { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(stringResource(MR.strings.delivery), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) mss.forEach { (member, status, sentViaProxy) -> MemberDeliveryStatusView(member, status, sentViaProxy) } } } else { - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(stringResource(MR.strings.no_info_on_delivery), color = MaterialTheme.colors.secondary) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 9b1bb45d8f..81a0de7bb9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -509,11 +509,11 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() - verticalAlignment = Alignment.CenterVertically ) { Row( - Modifier.weight(1f).padding(end = DEFAULT_PADDING), + Modifier.weight(1f).padding(top = 8.dp, end = DEFAULT_PADDING, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - MemberProfileImage(size = DEFAULT_MIN_SECTION_ITEM_HEIGHT, member) + MemberProfileImage(size = 42.dp, member) Spacer(Modifier.width(DEFAULT_PADDING_HALF)) Column { Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt index 8b2e008ad3..47668c4fb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt @@ -80,7 +80,7 @@ private fun ShareListNavLinkLayout( click: () -> Unit, stopped: Boolean, ) { - SectionItemView(minHeight = 50.dp, click = click, disabled = stopped) { + SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = 8.dp), click = click, disabled = stopped) { chatLinkPreview() } Divider(Modifier.padding(horizontal = 8.dp)) @@ -98,9 +98,11 @@ private fun SharePreviewView(chat: Chat, disabled: Boolean) { horizontalArrangement = Arrangement.spacedBy(4.dp) ) { if (chat.chatInfo is ChatInfo.Local) { - ProfileImage(size = 46.dp, null, icon = MR.images.ic_folder_filled, color = NoteFolderIconColor) + ProfileImage(size = 42.dp, null, icon = MR.images.ic_folder_filled, color = NoteFolderIconColor) + } else if (chat.chatInfo is ChatInfo.Group) { + ProfileImage(size = 42.dp, chat.chatInfo.image, icon = MR.images.ic_supervised_user_circle_filled) } else { - ProfileImage(size = 46.dp, chat.chatInfo.image) + ProfileImage(size = 42.dp, chat.chatInfo.image) } Text( chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index cdf1766a25..886b82de7d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -94,10 +94,17 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe } if (appPlatform.isAndroid) { tryOrShowError("UserPicker", error = {}) { - UserPicker(chatModel, userPickerState, showSettings = false, showCancel = true, cancelClicked = { - chatModel.sharedContent.value = null - userPickerState.value = AnimatedViewState.GONE - }) + UserPicker( + chatModel, + userPickerState, + showSettings = false, + showCancel = true, + contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart, + cancelClicked = { + chatModel.sharedContent.value = null + userPickerState.value = AnimatedViewState.GONE + } + ) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index 109e5bc737..333c73e195 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -5,9 +5,7 @@ import SectionSpacer import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -66,7 +64,7 @@ fun DatabaseErrorView( Modifier.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING, bottom = DEFAULT_PADDING), style = MaterialTheme.typography.h1 ) - SectionView(null, padding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content) + SectionView(null, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), content = content) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index 080edd22b2..90f8299404 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -85,7 +85,8 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co Text( title.value, fontWeight = FontWeight.SemiBold, - maxLines = 1 + maxLines = 1, + overflow = TextOverflow.Ellipsis ) } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index facecd2398..37bf5b10b1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -21,15 +21,15 @@ import chat.simplex.common.views.usersettings.SettingsActionItemWithContent import chat.simplex.res.MR @Composable -fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) { +fun SectionView(title: String? = null, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = DEFAULT_PADDING, content: (@Composable ColumnScope.() -> Unit)) { Column { if (title != null) { Text( title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, - modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = DEFAULT_PADDING), fontSize = 12.sp + modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = headerBottomPadding), fontSize = 12.sp ) } - Column(Modifier.padding(padding).fillMaxWidth()) { content() } + Column(Modifier.padding(contentPadding).fillMaxWidth()) { content() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 419d3b6ed7..d2e8ac7a6c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -194,13 +194,13 @@ private fun RetryButton(onClick: () -> Unit) { @Composable private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState) { - SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase()) { + SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) { LinkTextView(connReqInvitation, true) } Spacer(Modifier.height(10.dp)) - SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase()) { + SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase(), headerBottomPadding = 5.dp) { SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() }) } @@ -242,14 +242,14 @@ fun AddContactLearnMoreButton() { @Composable private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState, pastedLink: MutableState, close: () -> Unit) { - SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase()) { + SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase(), headerBottomPadding = 5.dp) { PasteLinkView(rhId, pastedLink, showQRCodeScanner, close) } if (appPlatform.isAndroid) { Spacer(Modifier.height(10.dp)) - SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase()) { + SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase(), headerBottomPadding = 5.dp) { QRCodeScanner(showQRCodeScanner) { text -> withBGApi { val res = verify(rhId, text, close) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index b5349e826d..eb7fd7b6b5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -8,9 +8,7 @@ import SectionSpacer import SectionView import TextIconSpaced import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -149,7 +147,7 @@ private fun ConnectDesktop(deviceName: String, remoteCtrls: SnapshotStateList) { AppBarTitle(stringResource(MR.strings.verify_connection)) - SectionView(stringResource(MR.strings.connected_to_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.connected_to_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { CtrlDeviceNameText(session, rc) Spacer(Modifier.height(DEFAULT_PADDING_HALF)) CtrlDeviceVersionText(session) @@ -313,7 +311,7 @@ private fun CtrlDeviceVersionText(session: RemoteCtrlSession) { @Composable private fun ActiveSession(session: RemoteCtrlSession, rc: RemoteCtrlInfo, close: () -> Unit) { AppBarTitle(stringResource(MR.strings.connected_to_desktop)) - SectionView(stringResource(MR.strings.connected_desktop).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.connected_desktop).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Text(rc.deviceViewName) Spacer(Modifier.height(DEFAULT_PADDING_HALF)) CtrlDeviceVersionText(session) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index d8993307d2..3747ae047a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -53,7 +53,7 @@ expect fun AppearanceView(m: ChatModel) object AppearanceScope { @Composable fun ProfileImageSection() { - SectionView(stringResource(MR.strings.settings_section_title_profile_images).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.settings_section_title_profile_images).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { val image = remember { chatModel.currentUser }.value?.image Row(Modifier.padding(top = 10.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { val size = 60 @@ -86,7 +86,7 @@ object AppearanceScope { @Composable fun FontScaleSection() { val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) } - SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { Box(Modifier.size(60.dp) .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt index b97a686e22..e5116f9149 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt @@ -1,15 +1,12 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionItemView import SectionItemViewSpaceBetween import SectionItemViewWithoutMinPadding import SectionSpacer import SectionTextFooter import SectionView import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -64,7 +61,7 @@ private fun HiddenProfileLayout( .fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.hide_profile)) - SectionView(padding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { UserProfileRow(user) } SectionSpacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 74e6bf5910..5bcb0a545d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.text.input.* import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller @@ -264,7 +263,7 @@ fun SocksProxySettings( .fillMaxWidth() ) { AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { DefaultConfigurableTextField( hostUnsaved, stringResource(MR.strings.host_verb), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index 4a9af6e822..a7bf5920e4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -8,7 +8,6 @@ import SectionItemViewWithoutMinPadding import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -270,7 +269,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: ( @Composable fun ActionHeader(title: StringResource) { AppBarTitle(stringResource(title)) - SectionView(padding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { + SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { UserProfileRow(user) } SectionSpacer() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 38ffb137ed..36c7d180b5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -25,7 +25,6 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.delay import java.util.Locale -import kotlin.math.roundToInt @Composable actual fun AppearanceView(m: ChatModel) { @@ -44,7 +43,7 @@ fun AppearanceScope.AppearanceLayout( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.appearance_settings)) - SectionView(stringResource(MR.strings.settings_section_title_language), padding = PaddingValues()) { + SectionView(stringResource(MR.strings.settings_section_title_language), contentPadding = PaddingValues()) { val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } LangSelector(state) { state.value = it @@ -79,7 +78,7 @@ fun AppearanceScope.AppearanceLayout( @Composable fun DensityScaleSection() { val localDensityScale = remember { mutableStateOf(appPrefs.densityScale.get()) } - SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { Box(Modifier.size(60.dp) .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) From f1e8c65aa1442f32cb0fdbc20b22465ef6f3ebf8 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:06:21 +0000 Subject: [PATCH 04/12] android, desktop: using SemVer when checking for updates (#4768) * android, desktop: using SemVer when checking for updates * simplify * simplify * no comment * simplify * change --------- Co-authored-by: Evgeny Poberezkin --- .../common/views/helpers/AppUpdater.kt | 94 ++++++++++++++++--- .../kotlin/chat/simplex/app/SemVerTest.kt | 63 +++++++++++++ 2 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt index faef957705..ac69c41832 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt @@ -19,12 +19,76 @@ import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import okhttp3.OkHttpClient import okhttp3.Request import java.io.Closeable import java.io.File import java.net.InetSocketAddress import java.net.Proxy +import kotlin.math.min + +data class SemVer( + val major: Int, + val minor: Int, + val patch: Int, + val preRelease: String? = null, + val buildNumber: Int? = null, +): Comparable { + + val isNotStable: Boolean = preRelease != null + + override fun compareTo(other: SemVer?): Int { + if (other == null) return 1 + return when { + major != other.major -> major.compareTo(other.major) + minor != other.minor -> minor.compareTo(other.minor) + patch != other.patch -> patch.compareTo(other.patch) + preRelease != null && other.preRelease != null -> { + val pr = preRelease.compareTo(other.preRelease, ignoreCase = true) + when { + pr != 0 -> pr + buildNumber != null && other.buildNumber != null -> buildNumber.compareTo(other.buildNumber) + buildNumber != null -> -1 + other.buildNumber != null -> 1 + else -> 0 + } + } + preRelease != null -> -1 + other.preRelease != null -> 1 + else -> 0 + } + } + + companion object { + private val regex = Regex("^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([A-Za-z]+)\\.(\\d+))?\$") + fun from(tagName: String): SemVer? { + val trimmed = tagName.trimStart { it == 'v' } + val redacted = when { + trimmed.contains('-') && trimmed.substringBefore('-').count { it == '.' } == 1 -> "${trimmed.substringBefore('-')}.0-${trimmed.substringAfter('-')}" + trimmed.substringBefore('-').count { it == '.' } == 1 -> "${trimmed}.0" + else -> trimmed + } + val group = regex.matchEntire(redacted)?.groups + return if (group != null) { + SemVer( + major = group[1]?.value?.toIntOrNull() ?: return null, + minor = group[2]?.value?.toIntOrNull() ?: return null, + patch = group[3]?.value?.toIntOrNull() ?: return null, + preRelease = group[4]?.value, + buildNumber = group[5]?.value?.toIntOrNull(), + ) + } else { + null + } + } + + fun fromCurrentVersionName(): SemVer? { + val currentVersionName = if (appPlatform.isAndroid) BuildConfigCommon.ANDROID_VERSION_NAME else BuildConfigCommon.DESKTOP_VERSION_NAME + return from(currentVersionName) + } + } +} @Serializable data class GitHubRelease( @@ -34,12 +98,18 @@ data class GitHubRelease( val htmlUrl: String, val name: String, val draft: Boolean, - val prerelease: Boolean, + @SerialName("prerelease") + private val preRelease: Boolean, val body: String, @SerialName("published_at") val publishedAt: String, val assets: List -) +) { + @Transient + val semVer: SemVer? = SemVer.from(tagName) + + val isConsideredBeta: Boolean = preRelease || semVer == null || semVer.isNotStable +} @Serializable data class GitHubAsset( @@ -105,25 +175,25 @@ private fun createUpdateJob() { fun checkForUpdate() { Log.d(TAG, "Checking for update") + val currentSemVer = SemVer.fromCurrentVersionName() + if (currentSemVer == null) { + Log.e(TAG, "Current SemVer cannot be parsed") + return + } val client = setupHttpClient() try { val request = Request.Builder().url("https://api.github.com/repos/simplex-chat/simplex-chat/releases").addHeader("User-agent", "curl").build() client.newCall(request).execute().use { response -> response.body?.use { val body = it.string() - val releases = json.decodeFromString>(body).filterNot { it.draft } + val releases = json.decodeFromString>(body) val release = when (appPrefs.appUpdateChannel.get()) { - AppUpdatesChannel.STABLE -> releases.firstOrNull { !it.prerelease } - AppUpdatesChannel.BETA -> releases.firstOrNull() + AppUpdatesChannel.STABLE -> releases.firstOrNull { r -> !r.draft && !r.isConsideredBeta && currentSemVer < r.semVer } + AppUpdatesChannel.BETA -> releases.firstOrNull { r -> !r.draft && currentSemVer < r.semVer } AppUpdatesChannel.DISABLED -> return - } ?: return - val currentVersionName = "v" + (if (appPlatform.isAndroid) BuildConfigCommon.ANDROID_VERSION_NAME else BuildConfigCommon.DESKTOP_VERSION_NAME) - val redactedCurrentVersionName = when { - currentVersionName.contains('-') && currentVersionName.substringBefore('-').count { it == '.' } == 1 -> "${currentVersionName.substringBefore('-')}.0-${currentVersionName.substringAfter('-')}" - currentVersionName.substringBefore('-').count { it == '.' } == 1 -> "${currentVersionName}.0" - else -> currentVersionName } - if (release.tagName == appPrefs.appSkippedUpdate.get() || release.tagName == currentVersionName || release.tagName == redactedCurrentVersionName) { + + if (release == null || release.tagName == appPrefs.appSkippedUpdate.get()) { Log.d(TAG, "Skipping update because of the same version or skipped version") return } diff --git a/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt new file mode 100644 index 0000000000..561911773f --- /dev/null +++ b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SemVerTest.kt @@ -0,0 +1,63 @@ +package chat.simplex.app + +import chat.simplex.common.views.helpers.SemVer +import kotlin.test.Test +import kotlin.test.assertEquals + +// use this command for testing: +// ./gradlew desktopTest +class SemVerTest { + @Test + fun testValidSemVer() { + assertEquals(SemVer.from("1.0.0"), SemVer(1, 0, 0)) + assertEquals(SemVer.from("1.0"), SemVer(1, 0, 0)) + assertEquals(SemVer.from("v1.0"), SemVer(1, 0, 0)) + assertEquals(SemVer.from("v1.0-beta.1"), SemVer(1, 0, 0, "beta", 1)) + val r = listOf>( + "0.0.4" to SemVer(0, 0, 4), + "1.2.3" to SemVer(1, 2, 3), + "10.20.30" to SemVer(10, 20, 30), + "1.0.0-alpha.1" to SemVer(1, 0, 0, "alpha", buildNumber = 1), + "1.0.0" to SemVer(1, 0, 0), + "2.0.0" to SemVer(2, 0, 0), + "1.1.7" to SemVer(1, 1, 7), + "2.0.1-alpha.1227" to SemVer(2, 0, 1, "alpha", 1227), + ) + r.forEach { (value, correct) -> + assertEquals(SemVer.from(value), correct) + } + } + + @Test + fun testComparisonSemVer() { + assert(SemVer(0, 1, 0) == SemVer.from("0.1.0")) + assert(SemVer(1, 1, 0) == SemVer.from("v1.1.0")) + assert(SemVer(0, 1, 0) > SemVer(0, 0, 1)) + assert(SemVer(1, 0, 0) > SemVer(0, 100, 100)) + assert(SemVer(0, 200, 0) > SemVer(0, 100, 100)) + assert(SemVer(0, 1, 0, "beta") > SemVer(0, 1, 0, "alpha")) + assert(SemVer(0, 1, 0) > SemVer(0, 1, 0, "alpha")) + assert(SemVer(0, 1, 0) > SemVer(0, 1, 0, "beta")) + assert(SemVer(0, 1, 0) > SemVer(0, 1, 0, "beta.0")) + assert(SemVer(0, 1, 0, "beta", 1) > SemVer(0, 1, 0, "beta", 0)) + assert(SemVer(0, 1, 0, "beta", 11) > SemVer(0, 1, 0, "beta", 10)) + assert(SemVer(0, 1, 0, "beta", 11) > SemVer(0, 1, 0, "beta", 9)) + assert(SemVer(0, 1, 0, "beta.1") > SemVer(0, 1, 0, "alpha.2")) + assert(SemVer(1, 1, 0, "beta.1") > SemVer(0, 1, 0, "beta.1")) + assert(SemVer(1, 0, 0) > SemVer(1, 0, 0, "beta.1")) + assert(SemVer(1, 0, 0) > null) + assert(SemVer.from("v6.0.0")!! > SemVer.from("v6.0.0-beta.3")) + assert(SemVer.from("v6.0.0-beta.3")!! > SemVer.from("v6.0.0-beta.2")) + assert(SemVer.from("0.1.0") == SemVer.from("0.1.0")) + assert(SemVer.from("0.1.1")!! > SemVer.from("0.1.0")) + assert(SemVer.from("0.2.1")!! > SemVer.from("0.1.1")) + assert(SemVer.from("2.0.1")!! > SemVer.from("0.1.1")) + assert(SemVer.from("0.1.1-beta.0")!! > SemVer.from("0.1.0-beta.0")) + assert(SemVer.from("0.1.1-beta.0")!! == SemVer.from("0.1.1-beta.0")) + assert(SemVer.from("0.1.1-beta.1")!! > SemVer.from("0.1.1-beta.0")) + assert(SemVer.from("10.0.0-beta.12")!! > SemVer.from("1.1.1")) + assert(SemVer.from("1.1.1-beta.120")!! > SemVer.from("1.1.1-alpha.9")) + assert(SemVer.from("1.1.1-beta.120")!! > SemVer.from("1.1.1-alpha.120")) + assert(SemVer.from("2.0.1")!! > SemVer.from("0.1.1")) + } +} From 76cb9013f5d1fc3d6243d8c6f588f3778a0e16e1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:21:00 +0000 Subject: [PATCH 05/12] desktop: show only AppImage download option for those who running AppImage (#4774) --- .../kotlin/chat/simplex/common/views/helpers/AppUpdater.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt index ac69c41832..974578882d 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt @@ -368,13 +368,15 @@ private suspend fun downloadAsset(asset: GitHubAsset) { } } +private fun isRunningFromAppImage(): Boolean = System.getenv("APPIMAGE") != null + private fun isRunningFromFlatpak(): Boolean = System.getenv("container") == "flatpak" private fun chooseGitHubReleaseAssets(release: GitHubRelease): List { val res = if (isRunningFromFlatpak()) { // No need to show download options for Flatpak users emptyList() - } else if (Runtime.getRuntime().exec("which dpkg").onExit().join().exitValue() == 0) { + } else if (!isRunningFromAppImage() && Runtime.getRuntime().exec("which dpkg").onExit().join().exitValue() == 0) { // Show all available .deb packages and user will choose the one that works on his system (for Debian derivatives) release.assets.filter { it.name.lowercase().endsWith(".deb") } } else { From 121eaf60738b7b94d63b8b8548b04a4f183b0502 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:39:28 +0400 Subject: [PATCH 06/12] flatpak: update metainfo (#4784) * flatpak: update metainfo * flatpak: change release link and ol to ul --- .../flatpak/chat.simplex.simplex.metainfo.xml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index a74a7b86a0..4ac5f88e14 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,38 @@ + + https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html + +

New in v6.0.1-3:

+
    +
  • reduce app memory usage and start time.
  • +
  • faster sending files to groups.
  • +
  • fix rare delivery bug.
  • +
+

New in v6.0:

+

New chat experience:

+
    +
  • connect to your friends faster.
  • +
  • archive contacts to chat later.
  • +
  • delete up to 20 messages at once.
  • +
  • increase font size.
  • +
+

New media options:

+
    +
  • play from the chat list.
  • +
  • blur for better privacy.
  • +
+

Private routing:

+
    +
  • it protects your IP address and connections and is now enabled by default.
  • +
+

Connection and servers information:

+
    +
  • to control your network status and usage.
  • +
+
+
https://github.com/simplex-chat/simplex-chat/releases/tag/v6.0.0 From acb372a4ce0074374458a0bae1f5ac863d64f3d0 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:31:02 +0000 Subject: [PATCH 07/12] core: call uuid (#4777) * core: call uuid * fix * text * android, desktop * ios --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 2 +- .../Shared/Views/Call/ActiveCallView.swift | 8 ++-- .../Shared/Views/Call/CallController.swift | 42 +++++++++++-------- apps/ios/Shared/Views/Call/CallManager.swift | 26 ++++++------ apps/ios/Shared/Views/Call/WebRTC.swift | 6 +-- apps/ios/Shared/Views/Chat/ChatView.swift | 4 +- apps/ios/SimpleXChat/CallTypes.swift | 5 +-- .../simplex/app/views/call/CallActivity.kt | 1 + .../simplex/common/views/call/CallManager.kt | 1 + .../views/call/IncomingCallAlertView.kt | 1 + .../chat/simplex/common/views/call/WebRTC.kt | 2 + .../simplex/common/views/chat/ChatView.kt | 2 +- package.yaml | 1 + simplex-chat.cabal | 8 ++++ src/Simplex/Chat.hs | 18 ++++---- src/Simplex/Chat/Call.hs | 2 + .../Chat/Migrations/M20240827_calls_uuid.hs | 18 ++++++++ src/Simplex/Chat/Migrations/chat_schema.sql | 2 + src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/Store/Profiles.hs | 14 +++---- 20 files changed, 107 insertions(+), 60 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 239ef7916e..797e68db4f 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2199,7 +2199,7 @@ func activateCall(_ callInvitation: RcvCallInvitation) { CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in if let error = error { DispatchQueue.main.async { - m.callInvitations[callInvitation.contact.id]?.callkitUUID = nil + m.callInvitations[callInvitation.contact.id]?.callUUID = nil } logger.error("reportNewIncomingCall error: \(error.localizedDescription)") } else { diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 97415018bf..d238c2dbae 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -185,7 +185,7 @@ struct ActiveCallView: View { case .ended: closeCallView(client) call.callState = .ended - if let uuid = call.callkitUUID { + if let uuid = call.callUUID { CallController.shared.endCall(callUUID: uuid) } case .ok: @@ -382,7 +382,7 @@ struct ActiveCallOverlay: View { private func endCallButton() -> some View { let cc = CallController.shared return callButton("phone.down.fill", width: 60, height: 60) { - if let uuid = call.callkitUUID { + if let uuid = call.callUUID { cc.endCall(callUUID: uuid) } else { cc.endCall(call: call) {} @@ -462,9 +462,9 @@ struct ActiveCallOverlay: View { struct ActiveCallOverlay_Previews: PreviewProvider { static var previews: some View { Group{ - ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .video), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) + ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, localMedia: .video), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) .background(.black) - ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .audio), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) + ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, localMedia: .audio), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) .background(.black) } } diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index a8a91057fa..bfa26700e5 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -51,7 +51,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXStartCallAction) { logger.debug("CallController.provider CXStartCallAction") - if callManager.startOutgoingCall(callUUID: action.callUUID) { + if callManager.startOutgoingCall(callUUID: action.callUUID.uuidString.lowercased()) { action.fulfill() provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil) } else { @@ -61,7 +61,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { logger.debug("CallController.provider CXAnswerCallAction") - if callManager.answerIncomingCall(callUUID: action.callUUID) { + if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) { // WebRTC call should be in connected state to fulfill. // Otherwise no audio and mic working on lockscreen fulfillOnConnect = action @@ -75,7 +75,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse // Should be nil here if connection was in connected state fulfillOnConnect?.fail() fulfillOnConnect = nil - callManager.endCall(callUUID: action.callUUID) { ok in + callManager.endCall(callUUID: action.callUUID.uuidString.lowercased()) { ok in if ok { action.fulfill() } else { @@ -86,7 +86,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { - if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID) { + if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID.uuidString.lowercased()) { action.fulfill() } else { action.fail() @@ -194,7 +194,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse if let contactId = payload.dictionaryPayload["contactId"] as? String, let invitation = m.callInvitations[contactId] { let update = self.cxCallUpdate(invitation: invitation) - if let uuid = invitation.callkitUUID { + if let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { logger.debug("CallController: report pushkit call via CallKit") let update = self.cxCallUpdate(invitation: invitation) self.provider.reportNewIncomingCall(with: uuid, update: update) { error in @@ -239,8 +239,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) { - logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID))") - if CallController.useCallKit(), let uuid = invitation.callkitUUID { + logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callUUID))") + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { if invitation.callTs.timeIntervalSinceNow >= -180 { let update = cxCallUpdate(invitation: invitation) provider.reportNewIncomingCall(with: uuid, update: update, completion: completion) @@ -272,14 +272,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) { logger.debug("CallController: reporting outgoing call connected") - if CallController.useCallKit(), let uuid = call.callkitUUID { + if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected) } } func reportCallRemoteEnded(invitation: RcvCallInvitation) { logger.debug("CallController: reporting remote ended") - if CallController.useCallKit(), let uuid = invitation.callkitUUID { + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) } else if invitation.contact.id == activeCallInvitation?.contact.id { activeCallInvitation = nil @@ -288,14 +288,17 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func reportCallRemoteEnded(call: Call) { logger.debug("CallController: reporting remote ended") - if CallController.useCallKit(), let uuid = call.callkitUUID { + if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) } } func startCall(_ contact: Contact, _ media: CallMediaType) { logger.debug("CallController.startCall") - let uuid = callManager.newOutgoingCall(contact, media) + let callUUID = callManager.newOutgoingCall(contact, media) + guard let uuid = UUID(uuidString: callUUID) else { + return + } if CallController.useCallKit() { let handle = CXHandle(type: .generic, value: contact.id) let action = CXStartCallAction(call: uuid, handle: handle) @@ -307,8 +310,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse update.localizedCallerName = contact.displayName self.provider.reportCall(with: uuid, updated: update) } - } else if callManager.startOutgoingCall(callUUID: uuid) { - if callManager.startOutgoingCall(callUUID: uuid) { + } else if callManager.startOutgoingCall(callUUID: callUUID) { + if callManager.startOutgoingCall(callUUID: callUUID) { logger.debug("CallController.startCall: call started") } else { logger.error("CallController.startCall: no active call") @@ -318,8 +321,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func answerCall(invitation: RcvCallInvitation) { logger.debug("CallController: answering a call") - if CallController.useCallKit(), let callUUID = invitation.callkitUUID { - requestTransaction(with: CXAnswerCallAction(call: callUUID)) + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { + requestTransaction(with: CXAnswerCallAction(call: uuid)) } else { callManager.answerIncomingCall(invitation: invitation) } @@ -328,10 +331,13 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } } - func endCall(callUUID: UUID) { - logger.debug("CallController: ending the call with UUID \(callUUID.uuidString)") + func endCall(callUUID: String) { + let uuid = UUID(uuidString: callUUID) + logger.debug("CallController: ending the call with UUID \(callUUID)") if CallController.useCallKit() { - requestTransaction(with: CXEndCallAction(call: callUUID)) + if let uuid { + requestTransaction(with: CXEndCallAction(call: uuid)) + } } else { callManager.endCall(callUUID: callUUID) { ok in if ok { diff --git a/apps/ios/Shared/Views/Call/CallManager.swift b/apps/ios/Shared/Views/Call/CallManager.swift index a6d5ea17c4..f3021815af 100644 --- a/apps/ios/Shared/Views/Call/CallManager.swift +++ b/apps/ios/Shared/Views/Call/CallManager.swift @@ -10,17 +10,17 @@ import Foundation import SimpleXChat class CallManager { - func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> UUID { - let uuid = UUID() - let call = Call(direction: .outgoing, contact: contact, callkitUUID: uuid, callState: .waitCapabilities, localMedia: media) + func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> String { + let uuid = UUID().uuidString.lowercased() + let call = Call(direction: .outgoing, contact: contact, callUUID: uuid, callState: .waitCapabilities, localMedia: media) call.speakerEnabled = media == .video ChatModel.shared.activeCall = call return uuid } - func startOutgoingCall(callUUID: UUID) -> Bool { + func startOutgoingCall(callUUID: String) -> Bool { let m = ChatModel.shared - if let call = m.activeCall, call.callkitUUID == callUUID { + if let call = m.activeCall, call.callUUID == callUUID { m.showCallView = true Task { await m.callCommand.processCommand(.capabilities(media: call.localMedia)) } return true @@ -28,7 +28,7 @@ class CallManager { return false } - func answerIncomingCall(callUUID: UUID) -> Bool { + func answerIncomingCall(callUUID: String) -> Bool { if let invitation = getCallInvitation(callUUID) { answerIncomingCall(invitation: invitation) return true @@ -42,7 +42,7 @@ class CallManager { let call = Call( direction: .incoming, contact: invitation.contact, - callkitUUID: invitation.callkitUUID, + callUUID: invitation.callUUID, callState: .invitationAccepted, localMedia: invitation.callType.media, sharedKey: invitation.sharedKey @@ -68,8 +68,8 @@ class CallManager { } } - func enableMedia(media: CallMediaType, enable: Bool, callUUID: UUID) -> Bool { - if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { + func enableMedia(media: CallMediaType, enable: Bool, callUUID: String) -> Bool { + if let call = ChatModel.shared.activeCall, call.callUUID == callUUID { let m = ChatModel.shared Task { await m.callCommand.processCommand(.media(media: media, enable: enable)) } return true @@ -77,8 +77,8 @@ class CallManager { return false } - func endCall(callUUID: UUID, completed: @escaping (Bool) -> Void) { - if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { + func endCall(callUUID: String, completed: @escaping (Bool) -> Void) { + if let call = ChatModel.shared.activeCall, call.callUUID == callUUID { endCall(call: call) { completed(true) } } else if let invitation = getCallInvitation(callUUID) { endCall(invitation: invitation) { completed(true) } @@ -126,8 +126,8 @@ class CallManager { } } - private func getCallInvitation(_ callUUID: UUID) -> RcvCallInvitation? { - if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callkitUUID == callUUID }) { + private func getCallInvitation(_ callUUID: String) -> RcvCallInvitation? { + if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callUUID == callUUID }) { return invitation } return nil diff --git a/apps/ios/Shared/Views/Call/WebRTC.swift b/apps/ios/Shared/Views/Call/WebRTC.swift index 333dc082d5..ba990981a1 100644 --- a/apps/ios/Shared/Views/Call/WebRTC.swift +++ b/apps/ios/Shared/Views/Call/WebRTC.swift @@ -18,7 +18,7 @@ class Call: ObservableObject, Equatable { var direction: CallDirection var contact: Contact - var callkitUUID: UUID? + var callUUID: String? var localMedia: CallMediaType @Published var callState: CallState @Published var localCapabilities: CallCapabilities? @@ -33,14 +33,14 @@ class Call: ObservableObject, Equatable { init( direction: CallDirection, contact: Contact, - callkitUUID: UUID?, + callUUID: String?, callState: CallState, localMedia: CallMediaType, sharedKey: String? = nil ) { self.direction = direction self.contact = contact - self.callkitUUID = callkitUUID + self.callUUID = callUUID self.callState = callState self.localMedia = localMedia self.sharedKey = sharedKey diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 655dd8aaed..d65fbc1ed6 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -568,8 +568,8 @@ struct ChatView: View { private func endCallButton(_ call: Call) -> some View { Button { - if let uuid = call.callkitUUID { - CallController.shared.endCall(callUUID: uuid) + if CallController.useCallKit(), let callUUID = call.callUUID { + CallController.shared.endCall(callUUID: callUUID) } else { CallController.shared.endCall(call: call) {} } diff --git a/apps/ios/SimpleXChat/CallTypes.swift b/apps/ios/SimpleXChat/CallTypes.swift index 227a1fbda5..9f6d98e518 100644 --- a/apps/ios/SimpleXChat/CallTypes.swift +++ b/apps/ios/SimpleXChat/CallTypes.swift @@ -42,6 +42,7 @@ public struct RcvCallInvitation: Decodable { public var contact: Contact public var callType: CallType public var sharedKey: String? + public var callUUID: String? public var callTs: Date public var callTypeText: LocalizedStringKey { get { @@ -52,10 +53,8 @@ public struct RcvCallInvitation: Decodable { } } - public var callkitUUID: UUID? = UUID() - private enum CodingKeys: String, CodingKey { - case user, contact, callType, sharedKey, callTs + case user, contact, callType, sharedKey, callUUID, callTs } public static let sampleData = RcvCallInvitation( diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index b78f3ac518..323eb4417b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -424,6 +424,7 @@ fun PreviewIncomingCallLockScreenAlert() { ) { IncomingCallLockScreenAlertLayout( invitation = RcvCallInvitation( + callUUID = "", remoteHostId = null, user = User.sampleData, contact = Contact.sampleData, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index 285658ec1d..7704509148 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -47,6 +47,7 @@ class CallManager(val chatModel: ChatModel) { remoteHostId = invitation.remoteHostId, userProfile = userProfile, contact = invitation.contact, + callUUID = invitation.callUUID, callState = CallState.InvitationAccepted, localMedia = invitation.callType.media, sharedKey = invitation.sharedKey, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt index 829a849ddc..32681234fa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt @@ -115,6 +115,7 @@ fun PreviewIncomingCallAlertLayout() { contact = Contact.sampleData, callType = CallType(media = CallMediaType.Audio, capabilities = CallCapabilities(encryption = false)), sharedKey = null, + callUUID = "", callTs = Clock.System.now() ), chatModel = ChatModel, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index 0a7231370b..5332bc650e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -13,6 +13,7 @@ data class Call( val remoteHostId: Long?, val userProfile: Profile, val contact: Contact, + val callUUID: String?, val callState: CallState, val localMedia: CallMediaType, val localCapabilities: CallCapabilities? = null, @@ -105,6 +106,7 @@ sealed class WCallResponse { val contact: Contact, val callType: CallType, val sharedKey: String? = null, + val callUUID: String, val callTs: Instant ) { val callTypeText: String get() = generalGetString(when(callType.media) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index e90eed547d..a4fe622a6f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -544,7 +544,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) if (chatInfo is ChatInfo.Direct) { val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId) val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi - chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) + chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } diff --git a/package.yaml b/package.yaml index b0732e17ee..090933594d 100644 --- a/package.yaml +++ b/package.yaml @@ -48,6 +48,7 @@ dependencies: - tls >= 1.9.0 && < 1.10 - unliftio == 0.2.* - unliftio-core == 0.2.* + - uuid == 1.3.* - zip == 2.0.* flags: diff --git a/simplex-chat.cabal b/simplex-chat.cabal index fed3a884ce..877bec0af6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -146,6 +146,7 @@ library Simplex.Chat.Migrations.M20240510_chat_items_via_proxy Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays Simplex.Chat.Migrations.M20240528_quota_err_counter + Simplex.Chat.Migrations.M20240827_calls_uuid Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared @@ -229,6 +230,7 @@ library , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -292,6 +294,7 @@ executable simplex-bot , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -355,6 +358,7 @@ executable simplex-bot-advanced , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -421,6 +425,7 @@ executable simplex-broadcast-bot , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -485,6 +490,7 @@ executable simplex-chat , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , websockets ==0.12.* , zip ==2.0.* default-language: Haskell2010 @@ -555,6 +561,7 @@ executable simplex-directory-service , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) @@ -655,6 +662,7 @@ test-suite simplex-chat-test , tls >=1.9.0 && <1.10 , unliftio ==0.2.* , unliftio-core ==0.2.* + , uuid ==1.3.* , zip ==2.0.* default-language: Haskell2010 if flag(swift) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5899be6445..ac1d0ac601 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -55,6 +55,8 @@ import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime) import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime, nominalDay, nominalDiffTimeToSeconds) import Data.Time.Clock.System (systemToUTCTime) import Data.Word (Word32) +import qualified Data.UUID as UUID +import qualified Data.UUID.V4 as V4 import qualified Database.SQLite.Simple as SQL import Simplex.Chat.Archive import Simplex.Chat.Call @@ -1263,12 +1265,13 @@ processChatCommand' vr = \case withContactLock "sendCallInvitation" contactId $ do g <- asks random callId <- atomically $ CallId <$> C.randomBytes 16 g + callUUID <- UUID.toText <$> liftIO V4.nextRandom dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing let invitation = CallInvitation {callType, callDhPubKey = fst <$> dhKeyPair} callState = CallInvitationSent {localCallType = callType, localDhPrivKey = snd <$> dhKeyPair} (msg, _) <- sendDirectContactMessage user ct (XCallInv callId invitation) ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndCall CISCallPending 0) - let call' = Call {contactId, callId, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} + let call' = Call {contactId, callId, callUUID, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} call_ <- atomically $ TM.lookupInsert contactId call' calls forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) @@ -1338,13 +1341,13 @@ processChatCommand' vr = \case rcvCallInvitations <- rights <$> mapM rcvCallInvitation invs pure $ CRCallInvitations rcvCallInvitations where - callInvitation Call {contactId, callState, callTs} = case callState of - CallInvitationReceived {peerCallType, sharedKey} -> Just (contactId, callTs, peerCallType, sharedKey) + callInvitation Call {contactId, callUUID, callState, callTs} = case callState of + CallInvitationReceived {peerCallType, sharedKey} -> Just (contactId, callUUID, callTs, peerCallType, sharedKey) _ -> Nothing - rcvCallInvitation (contactId, callTs, peerCallType, sharedKey) = runExceptT . withFastStore $ \db -> do + rcvCallInvitation (contactId, callUUID, callTs, peerCallType, sharedKey) = runExceptT . withFastStore $ \db -> do user <- getUserByContactId db contactId contact <- getContact db vr user contactId - pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callTs} + pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callUUID, callTs} APIGetNetworkStatuses -> withUser $ \_ -> CRNetworkStatuses Nothing . map (uncurry ConnNetworkStatus) . M.toList <$> chatReadVar connNetworkStatuses APICallStatus contactId receivedStatus -> @@ -5955,9 +5958,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = g <- asks random dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing ci <- saveCallItem CISCallPending + callUUID <- UUID.toText <$> liftIO V4.nextRandom let sharedKey = C.Key . C.dhBytes' <$> (C.dh' <$> callDhPubKey <*> (snd <$> dhKeyPair)) callState = CallInvitationReceived {peerCallType = callType, localDhPubKey = fst <$> dhKeyPair, sharedKey} - call' = Call {contactId, callId, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} + call' = Call {contactId, callId, callUUID, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} calls <- asks currentCalls -- theoretically, the new call invitation for the current contact can mark the in-progress call as ended -- (and replace it in ChatController) @@ -5965,7 +5969,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> createCall db user call' $ chatItemTs' ci call_ <- atomically (TM.lookupInsert contactId call' calls) forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing - toView $ CRCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callTs = chatItemTs' ci} + toView $ CRCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callUUID, callTs = chatItemTs' ci} toView $ CRNewChatItem user $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci else featureRejected CFCalls where diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index 9968d170aa..882ec8ccd0 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -29,6 +29,7 @@ import Simplex.Messaging.Util (decodeJSON, encodeJSON) data Call = Call { contactId :: ContactId, callId :: CallId, + callUUID :: Text, chatItemId :: Int64, callState :: CallState, callTs :: UTCTime @@ -111,6 +112,7 @@ data RcvCallInvitation = RcvCallInvitation contact :: Contact, callType :: CallType, sharedKey :: Maybe C.Key, + callUUID :: Text, callTs :: UTCTime } deriving (Show) diff --git a/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs b/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs new file mode 100644 index 0000000000..eb1e8db65a --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240827_calls_uuid where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240827_calls_uuid :: Query +m20240827_calls_uuid = + [sql| +ALTER TABLE calls ADD COLUMN call_uuid TEXT NOT NULL DEFAULT ""; +|] + +down_m20240827_calls_uuid :: Query +down_m20240827_calls_uuid = + [sql| +ALTER TABLE calls DROP COLUMN call_uuid; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index fdbc44a9c3..25cf886384 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -415,6 +415,8 @@ CREATE TABLE calls( user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) + , + call_uuid TEXT NOT NULL DEFAULT "" ); CREATE TABLE commands( command_id INTEGER PRIMARY KEY AUTOINCREMENT, -- used as ACorrId diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 5c9082b361..be3f4027ca 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -110,6 +110,7 @@ import Simplex.Chat.Migrations.M20240501_chat_deleted import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays import Simplex.Chat.Migrations.M20240528_quota_err_counter +import Simplex.Chat.Migrations.M20240827_calls_uuid import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -219,7 +220,8 @@ schemaMigrations = ("20240501_chat_deleted", m20240501_chat_deleted, Just down_m20240501_chat_deleted), ("20240510_chat_items_via_proxy", m20240510_chat_items_via_proxy, Just down_m20240510_chat_items_via_proxy), ("20240515_rcv_files_user_approved_relays", m20240515_rcv_files_user_approved_relays, Just down_m20240515_rcv_files_user_approved_relays), - ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter) + ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter), + ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index fb87662c27..a29460d5b1 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -549,17 +549,17 @@ overwriteProtocolServers db User {userId} servers = protocol = decodeLatin1 $ strEncode $ protocolTypeI @p createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () -createCall db user@User {userId} Call {contactId, callId, chatItemId, callState} callTs = do +createCall db user@User {userId} Call {contactId, callId, callUUID, chatItemId, callState} callTs = do currentTs <- getCurrentTime deleteCalls db user contactId DB.execute db [sql| INSERT INTO calls - (contact_id, shared_call_id, chat_item_id, call_state, call_ts, user_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?) + (contact_id, shared_call_id, call_uuid, chat_item_id, call_state, call_ts, user_id, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?) |] - (contactId, callId, chatItemId, callState, callTs, userId, currentTs, currentTs) + (contactId, callId, callUUID, chatItemId, callState, callTs, userId, currentTs, currentTs) deleteCalls :: DB.Connection -> User -> ContactId -> IO () deleteCalls db User {userId} contactId = do @@ -572,13 +572,13 @@ getCalls db = db [sql| SELECT - contact_id, shared_call_id, chat_item_id, call_state, call_ts + contact_id, shared_call_id, call_uuid, chat_item_id, call_state, call_ts FROM calls ORDER BY call_ts ASC |] where - toCall :: (ContactId, CallId, ChatItemId, CallState, UTCTime) -> Call - toCall (contactId, callId, chatItemId, callState, callTs) = Call {contactId, callId, chatItemId, callState, callTs} + toCall :: (ContactId, CallId, Text, ChatItemId, CallState, UTCTime) -> Call + toCall (contactId, callId, callUUID, chatItemId, callState, callTs) = Call {contactId, callId, callUUID, chatItemId, callState, callTs} createCommand :: DB.Connection -> User -> Maybe Int64 -> CommandFunction -> IO CommandId createCommand db User {userId} connId commandFunction = do From dfe16991d01eda56c670d1f220c3fbb719411fd0 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:49:11 +0000 Subject: [PATCH 08/12] ios: make CallKit calls fire in time after cold start (#4787) * ios: make CallKit calls fire in time after cold start * longer wait period * uncomment * change * change * removed commented code * ios: update core library --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 9 +- .../Shared/Views/Call/CallController.swift | 102 +++++++++++++----- .../ios/SimpleX NSE/NotificationService.swift | 4 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++---- 4 files changed, 103 insertions(+), 52 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 797e68db4f..6c5d5504e6 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2182,9 +2182,11 @@ func refreshCallInvitations() async throws { } } -func justRefreshCallInvitations() throws { +func justRefreshCallInvitations() async throws { let callInvitations = try apiGetCallInvitationsSync() - ChatModel.shared.callInvitations = callsByChat(callInvitations) + await MainActor.run { + ChatModel.shared.callInvitations = callsByChat(callInvitations) + } } private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: RcvCallInvitation] { @@ -2194,8 +2196,9 @@ private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: Rcv } func activateCall(_ callInvitation: RcvCallInvitation) { - if !callInvitation.user.showNotifications { return } let m = ChatModel.shared + logger.debug("reportNewIncomingCall activeCallUUID \(String(describing: m.activeCall?.callUUID)) invitationUUID \(String(describing: callInvitation.callUUID))") + if !callInvitation.user.showNotifications || m.activeCall?.callUUID == callInvitation.callUUID { return } CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in if let error = error { DispatchQueue.main.async { diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index bfa26700e5..36887d6184 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -61,12 +61,30 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { logger.debug("CallController.provider CXAnswerCallAction") - if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) { - // WebRTC call should be in connected state to fulfill. - // Otherwise no audio and mic working on lockscreen - fulfillOnConnect = action - } else { - action.fail() + Task { + let chatIsReady = await waitUntilChatStarted(timeoutMs: 30_000, stepMs: 500) + logger.debug("CallController chat started \(chatIsReady) \(ChatModel.shared.chatInitialized) \(ChatModel.shared.chatRunning == true) \(String(describing: AppChatState.shared.value))") + if !chatIsReady { + action.fail() + return + } + if !ChatModel.shared.callInvitations.values.contains(where: { inv in inv.callUUID == action.callUUID.uuidString.lowercased() }) { + try? await justRefreshCallInvitations() + logger.debug("CallController: updated call invitations chat") + } + await MainActor.run { + logger.debug("CallController.provider will answer on call") + + if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) { + logger.debug("CallController.provider answered on call") + // WebRTC call should be in connected state to fulfill. + // Otherwise no audio and mic working on lockscreen + fulfillOnConnect = action + } else { + logger.debug("CallController.provider will fail the call") + action.fail() + } + } } } @@ -156,6 +174,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } } + private func waitUntilChatStarted(timeoutMs: UInt64, stepMs: UInt64) async -> Bool { + logger.debug("CallController waiting until chat started") + var t: UInt64 = 0 + repeat { + if ChatModel.shared.chatInitialized, ChatModel.shared.chatRunning == true, case .active = AppChatState.shared.value { + return true + } + _ = try? await Task.sleep(nanoseconds: stepMs * 1000000) + t += stepMs + } while t < timeoutMs + return false + } + @objc(pushRegistry:didUpdatePushCredentials:forType:) func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)") @@ -171,32 +202,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse self.reportExpiredCall(payload: payload, completion) return } - if (!ChatModel.shared.chatInitialized) { - logger.debug("CallController: initializing chat") - do { - try initializeChat(start: true, refreshInvitations: false) - } catch let error { - logger.error("CallController: initializing chat error: \(error)") - self.reportExpiredCall(payload: payload, completion) - return - } - } - logger.debug("CallController: initialized chat") - startChatForCall() - logger.debug("CallController: started chat") - self.shouldSuspendChat = true - // There are no invitations in the model, as it was processed by NSE - try? justRefreshCallInvitations() - logger.debug("CallController: updated call invitations chat") - // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") // Extract the call information from the push notification payload let m = ChatModel.shared if let contactId = payload.dictionaryPayload["contactId"] as? String, - let invitation = m.callInvitations[contactId] { - let update = self.cxCallUpdate(invitation: invitation) - if let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { + let displayName = payload.dictionaryPayload["displayName"] as? String, + let callUUID = payload.dictionaryPayload["callUUID"] as? String, + let uuid = UUID(uuidString: callUUID), + let callTsInterval = payload.dictionaryPayload["callTs"] as? TimeInterval, + let mediaStr = payload.dictionaryPayload["media"] as? String, + let media = CallMediaType(rawValue: mediaStr) { + let update = self.cxCallUpdate(contactId, displayName, media) + let callTs = Date(timeIntervalSince1970: callTsInterval) + if callTs.timeIntervalSinceNow >= -180 { logger.debug("CallController: report pushkit call via CallKit") - let update = self.cxCallUpdate(invitation: invitation) self.provider.reportNewIncomingCall(with: uuid, update: update) { error in if error != nil { m.callInvitations.removeValue(forKey: contactId) @@ -205,11 +223,31 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse completion() } } else { + logger.debug("CallController will expire call 1") self.reportExpiredCall(update: update, completion) } } else { + logger.debug("CallController will expire call 2") self.reportExpiredCall(payload: payload, completion) } + + //DispatchQueue.main.asyncAfter(deadline: .now() + 10) { + if (!ChatModel.shared.chatInitialized) { + logger.debug("CallController: initializing chat") + do { + try initializeChat(start: true, refreshInvitations: false) + } catch let error { + logger.error("CallController: initializing chat error: \(error)") + if let call = ChatModel.shared.activeCall { + self.endCall(call: call, completed: completion) + } + return + } + } + logger.debug("CallController: initialized chat") + startChatForCall() + logger.debug("CallController: started chat") + self.shouldSuspendChat = true } // This function fulfils the requirement to always report a call when PushKit notification is received, @@ -261,6 +299,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse return update } + private func cxCallUpdate(_ contactId: String, _ displayName: String, _ media: CallMediaType) -> CXCallUpdate { + let update = CXCallUpdate() + update.remoteHandle = CXHandle(type: .generic, value: contactId) + update.hasVideo = media == .video + update.localizedCallerName = displayName + return update + } + func reportIncomingCall(call: Call, connectedAt dateConnected: Date?) { logger.debug("CallController: reporting incoming call connected") if CallController.useCallKit() { diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 1a2a27ba9b..81d0c9eac1 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -339,7 +339,9 @@ class NotificationService: UNNotificationServiceExtension { CXProvider.reportNewIncomingVoIPPushPayload([ "displayName": invitation.contact.displayName, "contactId": invitation.contact.id, - "media": invitation.callType.media.rawValue + "callUUID": invitation.callUUID ?? "", + "media": invitation.callType.media.rawValue, + "callTs": invitation.callTs.timeIntervalSince1970 ]) { error in logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") deliver(error == nil ? nil : createCallInvitationNtf(invitation)) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 48689d1010..1a348fc93e 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5852C7A26FE009F2C7C /* libffi.a */; }; - E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5862C7A26FE009F2C7C /* libgmpxx.a */; }; - E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5872C7A26FE009F2C7C /* libgmp.a */; }; - E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */; }; - E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */; }; + E51ED5A82C7F5F4B009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */; }; + E51ED5A92C7F5F4B009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A42C7F5F4B009F2C7C /* libffi.a */; }; + E51ED5AA2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */; }; + E51ED5AB2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */; }; + E51ED5AC2C7F5F4B009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5A72C7F5F4B009F2C7C /* libgmp.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -550,11 +550,11 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E51ED5852C7A26FE009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E51ED5862C7A26FE009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5872C7A26FE009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a"; sourceTree = ""; }; - E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a"; sourceTree = ""; }; + E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED5A42C7F5F4B009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a"; sourceTree = ""; }; + E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a"; sourceTree = ""; }; + E51ED5A72C7F5F4B009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */, - E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */, + E51ED5A82C7F5F4B009F2C7C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */, + E51ED5AC2C7F5F4B009F2C7C /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E51ED5AB2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a in Frameworks */, + E51ED5A92C7F5F4B009F2C7C /* libffi.a in Frameworks */, + E51ED5AA2C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */, - E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5852C7A26FE009F2C7C /* libffi.a */, - E51ED5872C7A26FE009F2C7C /* libgmp.a */, - E51ED5862C7A26FE009F2C7C /* libgmpxx.a */, - E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */, - E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */, + E51ED5A42C7F5F4B009F2C7C /* libffi.a */, + E51ED5A72C7F5F4B009F2C7C /* libgmp.a */, + E51ED5A32C7F5F4B009F2C7C /* libgmpxx.a */, + E51ED5A62C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5-ghc9.6.3.a */, + E51ED5A52C7F5F4B009F2C7C /* libHSsimplex-chat-6.0.3.0-7BSMDwqB9CRFek7eb5Gzw5.a */, ); path = Libraries; sourceTree = ""; From 2fe3acf4dfc75478f15eb4d6603bd10f8feca057 Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 29 Aug 2024 12:01:29 +0100 Subject: [PATCH 09/12] fix android simulator build (#4795) --- .../kotlin/chat/simplex/common/views/call/CallView.android.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index d05172a7a1..22f0c8d70b 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -770,6 +770,7 @@ fun PreviewActiveCallOverlayVideo() { callState = CallState.Negotiated, localMedia = CallMediaType.Video, peerMedia = CallMediaType.Video, + callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "tcp"), RTCIceCandidate(RTCIceCandidateType.Host, "tcp") @@ -799,6 +800,7 @@ fun PreviewActiveCallOverlayAudio() { callState = CallState.Negotiated, localMedia = CallMediaType.Audio, peerMedia = CallMediaType.Audio, + callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "udp"), RTCIceCandidate(RTCIceCandidateType.Host, "udp") From 6edea46dade4221e2233002722b7a88089cb292f Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:15:11 +0000 Subject: [PATCH 10/12] android, desktop: improvement to a lock UI (#4769) * android, desktop: improvement to a lock UI * oneTime passcode screen which allows to pass verification while in call * change * unused line * don't ask to set up auth if already has --------- Co-authored-by: Evgeny Poberezkin --- .../helpers/LocalAuthentication.android.kt | 3 +- .../kotlin/chat/simplex/common/App.kt | 5 +-- .../kotlin/chat/simplex/common/AppLock.kt | 41 ++++++++++++------- .../chat/simplex/common/model/ChatModel.kt | 3 +- .../common/views/database/DatabaseView.kt | 1 + .../views/helpers/LocalAuthentication.kt | 4 +- .../simplex/common/views/helpers/ModalView.kt | 16 ++++++-- .../views/usersettings/PrivacySettings.kt | 36 ++++++++-------- .../common/views/usersettings/SettingsView.kt | 4 +- .../kotlin/chat/simplex/common/DesktopApp.kt | 4 +- .../helpers/LocalAuthentication.desktop.kt | 3 +- 11 files changed, 70 insertions(+), 50 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt index b238bdf7ca..07426c7fbf 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.android.kt @@ -14,6 +14,7 @@ actual fun authenticate( promptSubtitle: String, selfDestruct: Boolean, usingLAMode: LAMode, + oneTime: Boolean, completed: (LAResult) -> Unit ) { val activity = mainActivity.get() ?: return completed(LAResult.Error("")) @@ -27,7 +28,7 @@ actual fun authenticate( else -> completed(LAResult.Unavailable()) } LAMode.PASSCODE -> { - authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, completed) + authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, oneTime, completed) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 94ca307529..3cba89922d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -74,6 +74,7 @@ fun MainScreen() { LaunchedEffect(showAdvertiseLAAlert) { if ( !chatModel.controller.appPrefs.laNoticeShown.get() + && !appPrefs.performLA.get() && showAdvertiseLAAlert && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && chatModel.chats.size > 3 @@ -211,10 +212,8 @@ fun MainScreen() { } else { ActiveCallView() } - } else { - // It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked - ModalManager.fullscreen.showPasscodeInView() } + ModalManager.fullscreen.showOneTimePasscodeInView() AlertManager.privacySensitive.showInView() if (onboarding == OnboardingStage.OnboardingComplete) { LaunchedEffect(chatModel.currentUser.value, chatModel.appOpenUrl.value) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt index d6214c252c..c93fabec8b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt @@ -5,6 +5,7 @@ import androidx.compose.material.* import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.localauth.SetAppPasscodeView @@ -31,7 +32,7 @@ object AppLock { fun showLANotice(laNoticeShown: SharedPreference) { Log.d(TAG, "showLANotice") - if (!laNoticeShown.get()) { + if (!laNoticeShown.get() && !appPrefs.performLA.get()) { laNoticeShown.set(true) AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.la_notice_title_simplex_lock), @@ -57,6 +58,8 @@ object AppLock { private fun showChooseLAMode() { Log.d(TAG, "showLANotice") + if (appPrefs.performLA.get()) return + AlertManager.shared.showAlertDialogStacked( title = generalGetString(MR.strings.la_lock_mode), text = null, @@ -80,21 +83,23 @@ object AppLock { authenticate( generalGetString(MR.strings.auth_enable_simplex_lock), generalGetString(MR.strings.auth_confirm_credential), + oneTime = true, completed = { laResult -> when (laResult) { LAResult.Success -> { - m.performLA.value = true + m.showAuthScreen.value = true appPrefs.performLA.set(true) laTurnedOnAlert() } is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Error -> { - m.performLA.value = false - appPrefs.performLA.set(false) + m.showAuthScreen.value = false + // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on) + // appPrefs.performLA.set(false) laFailedAlert() } is LAResult.Unavailable -> { - m.performLA.value = false + m.showAuthScreen.value = false appPrefs.performLA.set(false) m.showAdvertiseLAUnavailableAlert.value = true } @@ -104,19 +109,22 @@ object AppLock { } private fun setPasscode() { + if (appPrefs.performLA.get()) return + val appPrefs = ChatController.appPrefs ModalManager.fullscreen.showCustomModal { close -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) { SetAppPasscodeView( submit = { - ChatModel.performLA.value = true + ChatModel.showAuthScreen.value = true appPrefs.performLA.set(true) appPrefs.laMode.set(LAMode.PASSCODE) laTurnedOnAlert() }, cancel = { - ChatModel.performLA.value = false - appPrefs.performLA.set(false) + ChatModel.showAuthScreen.value = false + // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on) + // appPrefs.performLA.set(false) laPasscodeNotSetAlert() }, close = close @@ -147,6 +155,7 @@ object AppLock { else generalGetString(MR.strings.auth_unlock), selfDestruct = true, + oneTime = false, completed = { laResult -> when (laResult) { LAResult.Success -> @@ -160,7 +169,7 @@ object AppLock { } is LAResult.Unavailable -> { userAuthorized.value = true - m.performLA.value = false + m.showAuthScreen.value = false m.controller.appPrefs.performLA.set(false) laUnavailableTurningOffAlert() } @@ -192,22 +201,23 @@ object AppLock { generalGetString(MR.strings.auth_confirm_credential) else "", + oneTime = true, completed = { laResult -> val prefPerformLA = m.controller.appPrefs.performLA when (laResult) { LAResult.Success -> { - m.performLA.value = true + m.showAuthScreen.value = true prefPerformLA.set(true) laTurnedOnAlert() } is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Error -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) laFailedAlert() } is LAResult.Unavailable -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) laUnavailableInstructionAlert() } @@ -227,12 +237,13 @@ object AppLock { generalGetString(MR.strings.auth_confirm_credential) else generalGetString(MR.strings.auth_disable_simplex_lock), + oneTime = true, completed = { laResult -> val prefPerformLA = m.controller.appPrefs.performLA val selfDestructPref = m.controller.appPrefs.selfDestruct when (laResult) { LAResult.Success -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) DatabaseUtils.ksAppPassword.remove() selfDestructPref.set(false) @@ -240,12 +251,12 @@ object AppLock { } is LAResult.Failed -> { /* Can be called multiple times on every failure */ } is LAResult.Error -> { - m.performLA.value = true + m.showAuthScreen.value = true prefPerformLA.set(true) laFailedAlert() } is LAResult.Unavailable -> { - m.performLA.value = false + m.showAuthScreen.value = false prefPerformLA.set(false) laUnavailableTurningOffAlert() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index e92b3d714a..5a1c46666d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -20,7 +20,6 @@ import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.flow.internal.ChannelFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.datetime.* @@ -98,7 +97,7 @@ object ChatModel { } ) } - val performLA by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) } + val showAuthScreen by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) } val showAdvertiseLAUnavailableAlert = mutableStateOf(false) val showChatPreviews by lazy { mutableStateOf(ChatController.appPrefs.privacyShowChatPreviews.get()) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 333fda307a..b287847ace 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -423,6 +423,7 @@ fun authStopChat(m: ChatModel, progressIndicator: MutableState? = null, authenticate( generalGetString(MR.strings.auth_stop_chat), generalGetString(MR.strings.auth_log_in_using_credential), + oneTime = true, completed = { laResult -> when (laResult) { LAResult.Success, is LAResult.Unavailable -> { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt index 022ee37589..28f6320ee7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.kt @@ -34,6 +34,7 @@ expect fun authenticate( promptSubtitle: String, selfDestruct: Boolean = false, usingLAMode: LAMode = ChatModel.controller.appPrefs.laMode.get(), + oneTime: Boolean, completed: (LAResult) -> Unit ) @@ -41,10 +42,11 @@ fun authenticateWithPasscode( promptTitle: String, promptSubtitle: String, selfDestruct: Boolean, + oneTime: Boolean, completed: (LAResult) -> Unit ) { val password = DatabaseUtils.ksAppPassword.get() ?: return completed(LAResult.Unavailable(generalGetString(MR.strings.la_no_app_password))) - ModalManager.fullscreen.showPasscodeCustomModal { close -> + ModalManager.fullscreen.showPasscodeCustomModal(oneTime) { close -> BackHandler { close() completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled))) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index bfd61a2add..8da73ab3ca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -69,6 +69,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { // Don't use mutableStateOf() here, because it produces this if showing from SimpleXAPI.startChat(): // java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied private var passcodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null) + private var onTimePasscodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null) fun showModal(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { val data = ModalData() @@ -105,9 +106,13 @@ class ModalManager(private val placement: ModalPlacement? = null) { } } - fun showPasscodeCustomModal(modal: @Composable (close: () -> Unit) -> Unit) { - Log.d(TAG, "ModalManager.showPasscodeCustomModal") - passcodeView.value = modal + fun showPasscodeCustomModal(oneTime: Boolean, modal: @Composable (close: () -> Unit) -> Unit) { + Log.d(TAG, "ModalManager.showPasscodeCustomModal, oneTime: $oneTime") + if (oneTime) { + onTimePasscodeView.value = modal + } else { + passcodeView.value = modal + } } fun hasModalsOpen() = modalCount.value > 0 @@ -179,6 +184,11 @@ class ModalManager(private val placement: ModalPlacement? = null) { passcodeView.collectAsState().value?.invoke { passcodeView.value = null } } + @Composable + fun showOneTimePasscodeInView() { + onTimePasscodeView.collectAsState().value?.invoke { onTimePasscodeView.value = null } + } + /** * Allows to modify a list without getting [ConcurrentModificationException] * */ diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 084dfc20d2..abf318390f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -1,12 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionTextFooter import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -34,8 +32,6 @@ import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.model.ChatModel import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* -import kotlin.math.min -import kotlin.math.roundToInt enum class LAMode { SYSTEM, @@ -374,7 +370,8 @@ fun SimplexLockView( currentLAMode: SharedPreference, setPerformLA: (Boolean) -> Unit ) { - val performLA = remember { chatModel.performLA } + val showAuthScreen = remember { chatModel.showAuthScreen } + val performLA = remember { appPrefs.performLA.state } val laMode = remember { chatModel.controller.appPrefs.laMode.state } val laLockDelay = remember { chatModel.controller.appPrefs.laLockDelay } val showChangePasscode = remember { derivedStateOf { performLA.value && currentLAMode.state.value == LAMode.PASSCODE } } @@ -382,13 +379,9 @@ fun SimplexLockView( val selfDestructDisplayName = remember { mutableStateOf(chatModel.controller.appPrefs.selfDestructDisplayName.get() ?: "") } val selfDestructDisplayNamePref = remember { chatModel.controller.appPrefs.selfDestructDisplayName } - fun resetLAEnabled(onOff: Boolean) { - chatModel.controller.appPrefs.performLA.set(onOff) - chatModel.performLA.value = onOff - } - fun disableUnavailableLA() { - resetLAEnabled(false) + chatModel.controller.appPrefs.performLA.set(false) + chatModel.showAuthScreen.value = false currentLAMode.set(LAMode.default) laUnavailableInstructionAlert() } @@ -405,7 +398,8 @@ fun SimplexLockView( } else { generalGetString(MR.strings.chat_lock) }, - generalGetString(MR.strings.change_lock_mode) + generalGetString(MR.strings.change_lock_mode), + oneTime = true, ) { laResult -> when (laResult) { is LAResult.Error -> { @@ -415,7 +409,7 @@ fun SimplexLockView( LAResult.Success -> { when (toLAMode) { LAMode.SYSTEM -> { - authenticate(generalGetString(MR.strings.auth_enable_simplex_lock), promptSubtitle = "", usingLAMode = toLAMode) { laResult -> + authenticate(generalGetString(MR.strings.auth_enable_simplex_lock), promptSubtitle = "", usingLAMode = toLAMode, oneTime = true) { laResult -> when (laResult) { LAResult.Success -> { currentLAMode.set(toLAMode) @@ -451,7 +445,7 @@ fun SimplexLockView( } fun toggleSelfDestruct(selfDestruct: SharedPreference) { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_mode)) { laResult -> + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_mode), oneTime = true) { laResult -> when (laResult) { is LAResult.Error -> laFailedAlert() is LAResult.Failed -> { /* Can be called multiple times on every failure */ } @@ -470,7 +464,7 @@ fun SimplexLockView( } fun changeLAPassword() { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.la_change_app_passcode)) { laResult -> + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.la_change_app_passcode), oneTime = true) { laResult -> when (laResult) { LAResult.Success -> { ModalManager.fullscreen.showCustomModal { close -> @@ -494,7 +488,7 @@ fun SimplexLockView( } fun changeSelfDestructPassword() { - authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_passcode)) { laResult -> + authenticate(generalGetString(MR.strings.la_current_app_passcode), generalGetString(MR.strings.change_self_destruct_passcode), oneTime = true) { laResult -> when (laResult) { LAResult.Success -> { ModalManager.fullscreen.showCustomModal { close -> @@ -525,8 +519,8 @@ fun SimplexLockView( ) { AppBarTitle(stringResource(MR.strings.chat_lock)) SectionView { - EnableLock(performLA) { performLAToggle -> - performLA.value = performLAToggle + EnableLock(remember { appPrefs.performLA.state }) { performLAToggle -> + showAuthScreen.value = performLAToggle chatModel.controller.appPrefs.laNoticeShown.set(true) if (performLAToggle) { when (currentLAMode.state.value) { @@ -543,7 +537,9 @@ fun SimplexLockView( passcodeAlert(generalGetString(MR.strings.passcode_set)) }, cancel = { - resetLAEnabled(false) + chatModel.showAuthScreen.value = false + // Don't drop auth pref in case of state inconsistency (eg, you have set passcode but somehow bypassed toggle and turned it off and then on) + // chatModel.controller.appPrefs.performLA.set(false) }, close = close ) @@ -660,7 +656,7 @@ private fun EnableSelfDestruct( } @Composable -private fun EnableLock(performLA: MutableState, onCheckedChange: (Boolean) -> Unit) { +private fun EnableLock(performLA: State, onCheckedChange: (Boolean) -> Unit) { SectionItemView { Row(verticalAlignment = Alignment.CenterVertically) { Text( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index b50e905f39..3e1522b288 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.CreateProfile @@ -234,7 +235,7 @@ fun ChatLockItem( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), setPerformLA: (Boolean) -> Unit ) { - val performLA = remember { ChatModel.performLA } + val performLA = remember { appPrefs.performLA.state } val currentLAMode = remember { ChatModel.controller.appPrefs.laMode } SettingsActionItemWithContent( click = showSettingsModal { SimplexLockView(ChatModel, currentLAMode, setPerformLA) }, @@ -505,6 +506,7 @@ private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> authenticate( title, desc, + oneTime = true, completed = { laResult -> onFinish(laResult == LAResult.Success || laResult is LAResult.Unavailable) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index df8887c4e1..fc0e97b417 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH import chat.simplex.common.ui.theme.SimpleXTheme @@ -27,7 +26,6 @@ import kotlinx.coroutines.* import java.awt.event.WindowEvent import java.awt.event.WindowFocusListener import java.io.File -import kotlin.math.sqrt import kotlin.system.exitProcess val simplexWindowState = SimplexWindowState() @@ -172,7 +170,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { var windowFocused by remember { simplexWindowState.windowFocused } LaunchedEffect(windowFocused) { val delay = ChatController.appPrefs.laLockDelay.get() - if (!windowFocused && ChatModel.performLA.value && delay > 0) { + if (!windowFocused && ChatModel.showAuthScreen.value && delay > 0) { delay(delay * 1000L) // Trigger auth state check when delay ends (and if it ends) AppLock.recheckAuthState() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt index a251b7dc20..e245efae03 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/LocalAuthentication.desktop.kt @@ -7,10 +7,11 @@ actual fun authenticate( promptSubtitle: String, selfDestruct: Boolean, usingLAMode: LAMode, + oneTime: Boolean, completed: (LAResult) -> Unit ) { when (usingLAMode) { - LAMode.PASSCODE -> authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, completed) + LAMode.PASSCODE -> authenticateWithPasscode(promptTitle, promptSubtitle, selfDestruct, oneTime, completed) else -> {} } } From 122387d180322b06513393fc145b59e9e8f2038a Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:11:26 +0000 Subject: [PATCH 11/12] android, desktop: fix loading chat items when search was not empty (#4802) --- .../commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index a4fe622a6f..c8ad89609d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -86,6 +86,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - .collect { chatId -> markUnreadChatAsRead(chatId) showSearch.value = false + searchText.value = "" selectedChatItems.value = null } } From a9ec1f9ec1f1fbf8f196771d9ad88ec298464699 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 30 Aug 2024 13:39:35 +0100 Subject: [PATCH 12/12] core: 6.0.4.0 (simplexmq 6.0.3.0) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat.hs | 2 +- tests/ChatClient.hs | 1 + tests/ProtocolTests.hs | 4 ++-- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cabal.project b/cabal.project index 9bf39c2841..d7f6b67eb1 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 56986f82c89b04beae84a61208db8b55eb0098e3 + tag: d559a66145cf7b4cd367c09974ed1ce8393940b2 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 090933594d..947589acd0 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.3.0 +version: 6.0.4.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 0569199515..0f6592086f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."56986f82c89b04beae84a61208db8b55eb0098e3" = "0vqvdnm560xrfq7kjsghdbpk67vn4hcdpp58dfqgh9l2c9f79bin"; + "https://github.com/simplex-chat/simplexmq.git"."d559a66145cf7b4cd367c09974ed1ce8393940b2" = "1jav7jmriims6vlkxg8gmal03f9mbgrwc8v6g0rp95ivkx8gfjyw"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 877bec0af6..b3cde5ae9f 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: 6.0.3.0 +version: 6.0.4.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index ac1d0ac601..cfc9f0dc97 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3522,7 +3522,7 @@ agentSubscriber = do toView' $ CRChatError Nothing $ ChatErrorAgent (CRITICAL True $ "Message reception stopped: " <> show e) Nothing E.throwIO e where - process :: (ACorrId, EntityId, AEvt) -> CM' () + process :: (ACorrId, AEntityId, AEvt) -> CM' () process (corrId, entId, AEvt e msg) = run $ case e of SAENone -> processAgentMessageNoConn msg SAEConn -> processAgentMessage corrId entId msg diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index e3d557166b..42c12f1c6e 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -442,6 +442,7 @@ smpServerCfg = logStatsStartTime = 0, serverStatsLogFile = "tests/smp-server-stats.daily.log", serverStatsBackupFile = Nothing, + pendingENDInterval = 500000, smpServerVRange = supportedServerSMPRelayVRange, transportConfig = defaultTransportServerConfig {alpn = Just supportedSMPHandshakes}, smpHandshakeTimeout = 1000000, diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index d9552452cd..f64efe108f 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -16,7 +16,7 @@ import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet -import Simplex.Messaging.Protocol (supportedSMPClientVRange) +import Simplex.Messaging.Protocol (EntityId (..), supportedSMPClientVRange) import Simplex.Messaging.ServiceScheme import Simplex.Messaging.Version import Test.Hspec @@ -33,7 +33,7 @@ queue = supportedSMPClientVRange SMPQueueAddress { smpServer = srv, - senderId = "\223\142z\251", + senderId = EntityId "\223\142z\251", dhPublicKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=", sndSecure = False }