From 4e861cc93a459e927c7ddc3422ed430eb9628027 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 22 Aug 2023 19:16:38 +0100 Subject: [PATCH 1/6] core: only send changed contacts in api response on user profile update (#2971) --- src/Simplex/Chat.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d02455f71e..f701d65031 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1852,14 +1852,14 @@ processChatCommand = \case summary <- foldM (processAndCount user' logLevel) (UserProfileUpdateSummary 0 0 0 []) contacts pure $ CRUserProfileUpdated user' (fromLocalProfile p) p' summary where - processAndCount user' ll (!s@UserProfileUpdateSummary {notChanged, updateSuccesses, updateFailures, changedContacts}) ct = do + processAndCount user' ll (!s@UserProfileUpdateSummary {notChanged, updateSuccesses, updateFailures, changedContacts = cts}) ct = do let mergedProfile = userProfileToSend user Nothing $ Just ct ct' = updateMergedPreferences user' ct mergedProfile' = userProfileToSend user' Nothing $ Just ct' if mergedProfile' == mergedProfile then pure s {notChanged = notChanged + 1} else - let cts' = ct' : changedContacts + let cts' = if mergedPreferences ct == mergedPreferences ct' then cts else ct' : cts in (notifyContact mergedProfile' ct' $> s {updateSuccesses = updateSuccesses + 1, changedContacts = cts'}) `catchChatError` \e -> when (ll <= CLLInfo) (toView $ CRChatError (Just user) e) $> s {updateFailures = updateFailures + 1, changedContacts = cts'} where From 971e71727a4bbe74b684dd1bb2b6f2685141105e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 23 Aug 2023 13:24:34 +0100 Subject: [PATCH 2/6] core: specify compiler --- cabal.project | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cabal.project b/cabal.project index 619468642f..519633e1c2 100644 --- a/cabal.project +++ b/cabal.project @@ -2,6 +2,8 @@ packages: . -- packages: . ../simplexmq -- packages: . ../simplexmq ../direct-sqlcipher ../sqlcipher-simple +with-compiler: ghc-8.10.7 + constraints: zip +disable-bzip2 +disable-zstd source-repository-package From 95379404943b23963f4120675724e52dd0023c31 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 23 Aug 2023 19:11:39 +0300 Subject: [PATCH 3/6] ios: fullscreen player fix (#2968) * ios: fullscreen player fix * disable playing in some cases * simpler conditions * disabled video play button --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../Views/Chat/ChatItem/CIVideoView.swift | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index f9db2cf777..6de2e44b77 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -22,6 +22,7 @@ struct CIVideoView: View { @State private var scrollProxy: ScrollViewProxy? @State private var preview: UIImage? = nil @State private var player: AVPlayer? + @State private var fullPlayer: AVPlayer? @State private var url: URL? @State private var showFullScreenPlayer = false @State private var timeObserver: Any? = nil @@ -36,6 +37,7 @@ struct CIVideoView: View { self.scrollProxy = scrollProxy if let url = getLoadedVideo(chatItem.file) { self._player = State(initialValue: VideoPlayerView.getOrCreatePlayer(url, false)) + self._fullPlayer = State(initialValue: AVPlayer(url: url)) self._url = State(initialValue: url) } if let data = Data(base64Encoded: dropImagePrefix(image)), @@ -96,6 +98,7 @@ struct CIVideoView: View { DispatchQueue.main.async { videoWidth = w } return ZStack(alignment: .topTrailing) { ZStack(alignment: .center) { + let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete VideoPlayerView(player: player, url: url, showControls: false) .frame(width: w, height: w * preview.size.height / preview.size.width) .onChange(of: ChatModel.shared.stopPreviousRecPlay) { playingUrl in @@ -113,7 +116,9 @@ struct CIVideoView: View { player.pause() videoPlaying = false case .paused: - showFullScreenPlayer = true + if canBePlayed { + showFullScreenPlayer = true + } default: () } } @@ -122,8 +127,9 @@ struct CIVideoView: View { ChatModel.shared.stopPreviousRecPlay = url player.play() } label: { - playPauseIcon("play.fill") + playPauseIcon(canBePlayed ? "play.fill" : "play.slash") } + .disabled(!canBePlayed) } } loadingIndicator() @@ -258,8 +264,7 @@ struct CIVideoView: View { private func fullScreenPlayer(_ url: URL) -> some View { ZStack { Color.black.edgesIgnoringSafeArea(.all) - VideoPlayer(player: createFullScreenPlayerAndPlay(url)) { - } + VideoPlayer(player: fullPlayer) .overlay(alignment: .topLeading, content: { Button(action: { showFullScreenPlayer = false }, label: { @@ -282,28 +287,29 @@ struct CIVideoView: View { } } ) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now()) { + ChatModel.shared.stopPreviousRecPlay = url + if let player = fullPlayer { + player.play() + fullScreenTimeObserver = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in + player.seek(to: CMTime.zero) + player.play() + } + } + } + } .onDisappear { if let fullScreenTimeObserver = fullScreenTimeObserver { NotificationCenter.default.removeObserver(fullScreenTimeObserver) } fullScreenTimeObserver = nil + fullPlayer?.pause() + fullPlayer?.seek(to: CMTime.zero) } } } - private func createFullScreenPlayerAndPlay(_ url: URL) -> AVPlayer { - let player = AVPlayer(url: url) - DispatchQueue.main.asyncAfter(deadline: .now()) { - ChatModel.shared.stopPreviousRecPlay = url - player.play() - fullScreenTimeObserver = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in - player.seek(to: CMTime.zero) - player.play() - } - } - return player - } - private func addObserver(_ player: AVPlayer, _ url: URL) { timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.01, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { time in if let item = player.currentItem { From 9639fd26b8ff878f80a5cab289a36ccd95192318 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 23 Aug 2023 19:27:44 +0300 Subject: [PATCH 4/6] ios: making thumbnails faster (#2972) Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/ios/Shared/Views/Helpers/ImagePicker.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Helpers/ImagePicker.swift b/apps/ios/Shared/Views/Helpers/ImagePicker.swift index 21b968fde9..1b44c23135 100644 --- a/apps/ios/Shared/Views/Helpers/ImagePicker.swift +++ b/apps/ios/Shared/Views/Helpers/ImagePicker.swift @@ -133,7 +133,7 @@ struct LibraryMediaListPicker: UIViewControllerRepresentable { config.filter = .any(of: [.images, .videos]) config.selectionLimit = selectionLimit config.selection = .ordered - //config.preferredAssetRepresentationMode = .current + config.preferredAssetRepresentationMode = .current let controller = PHPickerViewController(configuration: config) controller.delegate = context.coordinator return controller From 16792de67ade0801a8a61f5eb048b4eb4af4d2b3 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:43:06 +0300 Subject: [PATCH 5/6] ios: update contacts on profile update (#2970) * ios: update contacts on profile update * change * remove init --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/ios/Shared/Model/SimpleXAPI.swift | 6 +++--- apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift | 2 +- apps/ios/Shared/Views/UserSettings/PreferencesView.swift | 3 ++- apps/ios/Shared/Views/UserSettings/UserProfile.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 4 ++-- apps/ios/SimpleXChat/ChatTypes.swift | 7 +++++++ 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 3efc9f102c..59524c2c39 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -691,12 +691,12 @@ func apiListContacts() throws -> [Contact] { throw r } -func apiUpdateProfile(profile: Profile) async throws -> Profile? { +func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? { let userId = try currentUserId("apiUpdateProfile") let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile)) switch r { case .userProfileNoChange: return nil - case let .userProfileUpdated(_, _, toProfile): return toProfile + case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts) default: throw r } } @@ -706,7 +706,7 @@ func apiSetProfileAddress(on: Bool) async throws -> User? { let r = await chatSendCmd(.apiSetProfileAddress(userId: userId, on: on)) switch r { case .userProfileNoChange: return nil - case let .userProfileUpdated(user, _, _): return user + case let .userProfileUpdated(user, _, _, _): return user default: throw r } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift index 32adf861f3..729556e739 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift @@ -130,7 +130,7 @@ struct GroupProfileView: View { let err = responseError(error) saveGroupError = err showSaveErrorAlert = true - logger.error("UserProfile apiUpdateProfile error: \(err)") + logger.error("GroupProfile apiUpdateGroup error: \(err)") } } } diff --git a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift index cf8978498b..960afb6d38 100644 --- a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift +++ b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift @@ -71,9 +71,10 @@ struct PreferencesView: View { do { var p = fromLocalProfile(profile) p.preferences = fullPreferencesToPreferences(preferences) - if let newProfile = try await apiUpdateProfile(profile: p) { + if let (newProfile, updatedContacts) = try await apiUpdateProfile(profile: p) { await MainActor.run { chatModel.updateCurrentUser(newProfile, preferences) + updatedContacts.forEach(chatModel.updateContact) currentPreferences = preferences } } diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 9ea6fe2a7f..f38dc593a6 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -144,7 +144,7 @@ struct UserProfile: View { func saveProfile() { Task { do { - if let newProfile = try await apiUpdateProfile(profile: profile) { + if let (newProfile, _) = try await apiUpdateProfile(profile: profile) { DispatchQueue.main.async { chatModel.updateCurrentUser(newProfile) profile = newProfile diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index ae92930669..d80626d6f1 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -457,7 +457,7 @@ public enum ChatResponse: Decodable, Error { case contactDeleted(user: UserRef, contact: Contact) case chatCleared(user: UserRef, chatInfo: ChatInfo) case userProfileNoChange(user: User) - case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile) + case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) case userPrivacy(user: User, updatedUser: User) case contactAliasUpdated(user: UserRef, toContact: Contact) case connectionAliasUpdated(user: UserRef, toConnection: PendingContactConnection) @@ -724,7 +724,7 @@ public enum ChatResponse: Decodable, Error { case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) case .userProfileNoChange: return noDetails - case let .userProfileUpdated(u, _, toProfile): return withUser(u, String(describing: toProfile)) + case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) case let .userPrivacy(u, updatedUser): return withUser(u, String(describing: updatedUser)) case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact)) case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index ef85978350..48996abb78 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -171,6 +171,13 @@ public func fromLocalProfile (_ profile: LocalProfile) -> Profile { Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences) } +public struct UserProfileUpdateSummary: Decodable { + public var notChanged: Int + public var updateSuccesses: Int + public var updateFailures: Int + public var changedContacts: [Contact] +} + public enum ChatType: String { case direct = "@" case group = "#" From 06369e277c4497c72ce459a4fcfb221cb4d6b081 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:43:21 +0300 Subject: [PATCH 6/6] android: update contacts on profile update (#2969) * android: update contacts on profile update * returned back * change --- .../kotlin/chat/simplex/common/model/ChatModel.kt | 8 ++++++++ .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 8 ++++---- .../chat/simplex/common/views/usersettings/Preferences.kt | 6 ++++-- .../simplex/common/views/usersettings/UserProfileView.kt | 5 +++-- 4 files changed, 19 insertions(+), 8 deletions(-) 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 c2ce1a2f45..629d4b8699 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 @@ -946,6 +946,14 @@ data class LocalProfile( } } +@Serializable +data class UserProfileUpdateSummary( + val notChanged: Int, + val updateSuccesses: Int, + val updateFailures: Int, + val changedContacts: List +) + @Serializable class Group ( val groupInfo: GroupInfo, 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 4d73a96b67..c5b11ef6de 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 @@ -910,11 +910,11 @@ object ChatController { return null } - suspend fun apiUpdateProfile(profile: Profile): Profile? { + suspend fun apiUpdateProfile(profile: Profile): Pair>? { val userId = kotlin.runCatching { currentUserId("apiUpdateProfile") }.getOrElse { return null } val r = sendCmd(CC.ApiUpdateProfile(userId, profile)) - if (r is CR.UserProfileNoChange) return profile - if (r is CR.UserProfileUpdated) return r.toProfile + if (r is CR.UserProfileNoChange) return profile to emptyList() + if (r is CR.UserProfileUpdated) return r.toProfile to r.updateSummary.changedContacts Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}") return null } @@ -3254,7 +3254,7 @@ sealed class CR { @Serializable @SerialName("contactDeleted") class ContactDeleted(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("chatCleared") class ChatCleared(val user: UserRef, val chatInfo: ChatInfo): CR() @Serializable @SerialName("userProfileNoChange") class UserProfileNoChange(val user: User): CR() - @Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val user: User, val fromProfile: Profile, val toProfile: Profile): CR() + @Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val user: User, val fromProfile: Profile, val toProfile: Profile, val updateSummary: UserProfileUpdateSummary): CR() @Serializable @SerialName("userPrivacy") class UserPrivacy(val user: User, val updatedUser: User): CR() @Serializable @SerialName("contactAliasUpdated") class ContactAliasUpdated(val user: UserRef, val toContact: Contact): CR() @Serializable @SerialName("connectionAliasUpdated") class ConnectionAliasUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index 91a1a2ec86..202602b287 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -25,9 +25,11 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) { fun savePrefs(afterSave: () -> Unit = {}) { withApi { val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences()) - val updatedProfile = m.controller.apiUpdateProfile(newProfile) - if (updatedProfile != null) { + val updated = m.controller.apiUpdateProfile(newProfile) + if (updated != null) { + val (updatedProfile, updatedContacts) = updated m.updateCurrentUser(updatedProfile, preferences) + updatedContacts.forEach(m::updateContact) currentPreferences = preferences } afterSave() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index 7f8edbd0fd..a7ae3a6751 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -39,8 +39,9 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { close, saveProfile = { displayName, fullName, image -> withApi { - val newProfile = chatModel.controller.apiUpdateProfile(profile.copy(displayName = displayName, fullName = fullName, image = image)) - if (newProfile != null) { + val updated = chatModel.controller.apiUpdateProfile(profile.copy(displayName = displayName, fullName = fullName, image = image)) + if (updated != null) { + val (newProfile, _) = updated chatModel.updateCurrentUser(newProfile) profile = newProfile }