diff --git a/NextcloudTalk.xcodeproj/project.pbxproj b/NextcloudTalk.xcodeproj/project.pbxproj index 6de286305..87e560f76 100644 --- a/NextcloudTalk.xcodeproj/project.pbxproj +++ b/NextcloudTalk.xcodeproj/project.pbxproj @@ -261,6 +261,8 @@ 1FD6F83E2B87B712004048AB /* NCUserStatusExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD6F83D2B87B712004048AB /* NCUserStatusExtensions.swift */; }; 1FD8AE6B2A3A216300787C16 /* UIRoomTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD8AD8C2A3A162100787C16 /* UIRoomTest.swift */; }; 1FD9182928C55A73009092AB /* BGTaskHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD9182828C55A73009092AB /* BGTaskHelper.swift */; }; + 1FDB47F62C9C71CE00D6F423 /* TalkAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FDB47F52C9C71CE00D6F423 /* TalkAccount.swift */; }; + 1FDB47F82C9C7E3F00D6F423 /* NCDatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FDB47F72C9C7E3F00D6F423 /* NCDatabaseManager.swift */; }; 1FDCC3D429EBF6E700DEB39B /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FDCC3D329EBF6E700DEB39B /* AvatarImageView.swift */; }; 1FDCC3E329EC787400DEB39B /* AvatarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F90DA0329E9A28E00E81E3D /* AvatarManager.swift */; }; 1FDCC3ED29EC7E6700DEB39B /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FDCC3D329EBF6E700DEB39B /* AvatarImageView.swift */; }; @@ -793,6 +795,8 @@ 1FD8AD8A2A3A162100787C16 /* NextcloudTalkUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudTalkUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1FD8AD8C2A3A162100787C16 /* UIRoomTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIRoomTest.swift; sourceTree = ""; }; 1FD9182828C55A73009092AB /* BGTaskHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGTaskHelper.swift; sourceTree = ""; }; + 1FDB47F52C9C71CE00D6F423 /* TalkAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalkAccount.swift; sourceTree = ""; }; + 1FDB47F72C9C7E3F00D6F423 /* NCDatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCDatabaseManager.swift; sourceTree = ""; }; 1FDCC3D329EBF6E700DEB39B /* AvatarImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = ""; }; 1FDCC3EC29EC7DD400DEB39B /* NextcloudTalk-Bridging-Header-Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NextcloudTalk-Bridging-Header-Extensions.h"; sourceTree = ""; }; 1FDCC3EF29ECB4CE00DEB39B /* AvatarButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarButton.swift; sourceTree = ""; }; @@ -1636,8 +1640,10 @@ children = ( 2C40281322832EED0000DDFC /* NCDatabaseManager.h */, 2C40281422832EED0000DDFC /* NCDatabaseManager.m */, + 1FDB47F72C9C7E3F00D6F423 /* NCDatabaseManager.swift */, 2C4446D12658147900DF1DBC /* TalkAccount.h */, 2C4446D22658147900DF1DBC /* TalkAccount.m */, + 1FDB47F52C9C71CE00D6F423 /* TalkAccount.swift */, 1F1B50452B90CDE600B0F2F4 /* TalkCapabilities.h */, 1F1B50462B90CDF800B0F2F4 /* TalkCapabilities.m */, 2C4446D6265814D100DF1DBC /* ServerCapabilities.h */, @@ -2942,6 +2948,7 @@ 2C444706265E59B100DF1DBC /* ShareConfirmationCollectionViewCell.m in Sources */, 1FCE3D552C9C189D009C68A9 /* NCChatFileControllerWrapper.swift in Sources */, 2C78EF991F80F81E008AFA74 /* NCSignalingController.m in Sources */, + 1FDB47F82C9C7E3F00D6F423 /* NCDatabaseManager.swift in Sources */, 2CB304202264775E0053078A /* UIResponder+SLKAdditions.m in Sources */, 2C8E2A1B232174C20022BFC9 /* MessageSeparatorTableViewCell.m in Sources */, 1FAB2EEE2AD1BC1B001214EB /* UIControlExtensions.swift in Sources */, @@ -2975,6 +2982,7 @@ 1FE7DE302BB4598F0040EE12 /* RoomInvitationViewCell.swift in Sources */, 1F77A6222AB9EB06007B6037 /* SocketConnection.m in Sources */, 1FF136152BFB74C3006A6101 /* NCChatMessage.swift in Sources */, + 1FDB47F62C9C71CE00D6F423 /* TalkAccount.swift in Sources */, 2CC1C38829C0945700C8436B /* DRCellSlideActionView.m in Sources */, 1FA732FC2966CBB7003D2103 /* CallFlowLayout.swift in Sources */, 2C78EF951F7E70EB008AFA74 /* NCPeerConnection.m in Sources */, diff --git a/NextcloudTalk/BaseChatViewController.swift b/NextcloudTalk/BaseChatViewController.swift index 6bee38ec4..15ff3444a 100644 --- a/NextcloudTalk/BaseChatViewController.swift +++ b/NextcloudTalk/BaseChatViewController.swift @@ -2358,6 +2358,8 @@ import QuickLook let activeAccount = NCDatabaseManager.sharedInstance().activeAccount() self.setTemporaryReaction(reaction: reaction, withState: .adding, toMessage: message) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: reaction, forAccount: activeAccount.accountId) + NCAPIController.sharedInstance().addReaction(reaction, toMessage: message.messageId, inRoom: self.room.token, for: activeAccount) { _, error, _ in if error != nil { NotificationPresenter.shared().present(text: NSLocalizedString("An error occurred while adding a reaction to a message", comment: ""), dismissAfterDelay: 5.0, includedStyle: .error) diff --git a/NextcloudTalk/ChatViewController.swift b/NextcloudTalk/ChatViewController.swift index 27da79832..df9409c32 100644 --- a/NextcloudTalk/ChatViewController.swift +++ b/NextcloudTalk/ChatViewController.swift @@ -1487,7 +1487,7 @@ import UIKit let reactionViewPadding = 10 let emojiButtonPadding = 10 let emojiButtonSize = 48 - let frequentlyUsedEmojis = ["👍", "❤️", "😂", "😅"] + let frequentlyUsedEmojis = NCDatabaseManager.sharedInstance().activeAccount().frequentlyUsedEmojis let totalEmojiButtonWidth = frequentlyUsedEmojis.count * emojiButtonSize let totalEmojiButtonPadding = frequentlyUsedEmojis.count * emojiButtonPadding diff --git a/NextcloudTalk/NCDatabaseManager.m b/NextcloudTalk/NCDatabaseManager.m index 867ccebe1..57e65e7cf 100644 --- a/NextcloudTalk/NCDatabaseManager.m +++ b/NextcloudTalk/NCDatabaseManager.m @@ -16,7 +16,7 @@ NSString *const kTalkDatabaseFolder = @"Library/Application Support/Talk"; NSString *const kTalkDatabaseFileName = @"talk.realm"; -uint64_t const kTalkDatabaseSchemaVersion = 68; +uint64_t const kTalkDatabaseSchemaVersion = 69; NSString * const kCapabilitySystemMessages = @"system-messages"; NSString * const kCapabilityNotificationLevels = @"notification-levels"; diff --git a/NextcloudTalk/NCDatabaseManager.swift b/NextcloudTalk/NCDatabaseManager.swift new file mode 100644 index 000000000..9a5f28f92 --- /dev/null +++ b/NextcloudTalk/NCDatabaseManager.swift @@ -0,0 +1,41 @@ +// +// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: GPL-3.0-or-later +// + +import Foundation + +@objc public extension NCDatabaseManager { + + func increaseEmojiUsage(forEmoji emojiString: String, forAccount accountId: String) { + guard let account = NCDatabaseManager.sharedInstance().talkAccount(forAccountId: accountId) else { return } + var newData: [String: Int]? + + if let data = account.frequentlyUsedEmojisJSONString.data(using: .utf8), + var emojiData = try? JSONSerialization.jsonObject(with: data) as? [String: Int] { + + if let currentEmojiCount = emojiData[emojiString] { + emojiData[emojiString] = currentEmojiCount + 1 + } else { + emojiData[emojiString] = 1 + } + + newData = emojiData + } else { + // No existing data, start new + newData = [emojiString: 1] + } + + guard let newData, let jsonData = try? JSONSerialization.data(withJSONObject: newData), + let jsonString = String(data: jsonData, encoding: .utf8) + else { return } + + let realm = RLMRealm.default() + + try? realm.transaction { + if let managedTalkAccount = TalkAccount.objects(where: "accountId = %@", account.accountId).firstObject() as? TalkAccount { + managedTalkAccount.frequentlyUsedEmojisJSONString = jsonString + } + } + } +} diff --git a/NextcloudTalk/TalkAccount.h b/NextcloudTalk/TalkAccount.h index ec4f22028..f36151e46 100644 --- a/NextcloudTalk/TalkAccount.h +++ b/NextcloudTalk/TalkAccount.h @@ -43,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN @property NSString *lastNotificationETag; @property NSInteger lastPendingFederationInvitationFetch; @property NSInteger pendingFederationInvitations; +@property NSString *frequentlyUsedEmojisJSONString; @end diff --git a/NextcloudTalk/TalkAccount.swift b/NextcloudTalk/TalkAccount.swift new file mode 100644 index 000000000..d73e0d59d --- /dev/null +++ b/NextcloudTalk/TalkAccount.swift @@ -0,0 +1,34 @@ +// +// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: GPL-3.0-or-later +// + +import Foundation +import SwiftyAttributes + +@objc extension TalkAccount { + + public var defaultEmojis: [String] { + return ["👍", "❤️", "😂", "😅"] + } + + public var frequentlyUsedEmojis: [String] { + guard let data = self.frequentlyUsedEmojisJSONString.data(using: .utf8), + let jsonData = try? JSONSerialization.jsonObject(with: data) as? [String: Int] + else { return defaultEmojis } + + // First sort by value (the amount), then by key (the emoji) + var emojis = jsonData.sorted(by: { + $0.value != $1.value ? + $0.value > $1.value : + $0.key < $1.key + }).prefix(4).map({ $0.key }) + + if emojis.count < 4 { + // Fill up to 4 emojis + emojis.append(contentsOf: defaultEmojis.prefix(4 - emojis.count)) + } + + return emojis + } +} diff --git a/NextcloudTalkTests/Unit/Chat/UnitChatViewControllerTest.swift b/NextcloudTalkTests/Unit/Chat/UnitChatViewControllerTest.swift index 684f15389..7a16fd7cb 100644 --- a/NextcloudTalkTests/Unit/Chat/UnitChatViewControllerTest.swift +++ b/NextcloudTalkTests/Unit/Chat/UnitChatViewControllerTest.swift @@ -68,4 +68,32 @@ final class UnitChatViewControllerTest: TestBaseRealm { XCTAssertEqual(NCChatMessage.allObjects().count, 0) } + + func testFrequentlyEmojis() throws { + var activeAccount = NCDatabaseManager.sharedInstance().activeAccount() + XCTAssertEqual(activeAccount.frequentlyUsedEmojis, ["👍", "❤️", "😂", "😅"]) + + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🙈", forAccount: activeAccount.accountId) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🙈", forAccount: activeAccount.accountId) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🙈", forAccount: activeAccount.accountId) + activeAccount = NCDatabaseManager.sharedInstance().activeAccount() + XCTAssertEqual(activeAccount.frequentlyUsedEmojis, ["🙈", "👍", "❤️", "😂"]) + + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🇫🇮", forAccount: activeAccount.accountId) + activeAccount = NCDatabaseManager.sharedInstance().activeAccount() + XCTAssertEqual(activeAccount.frequentlyUsedEmojis, ["🙈", "🇫🇮", "👍", "❤️"]) + + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🇫🇮", forAccount: activeAccount.accountId) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🇫🇮", forAccount: activeAccount.accountId) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🇫🇮", forAccount: activeAccount.accountId) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🇫🇮", forAccount: activeAccount.accountId) + activeAccount = NCDatabaseManager.sharedInstance().activeAccount() + XCTAssertEqual(activeAccount.frequentlyUsedEmojis, ["🇫🇮", "🙈", "👍", "❤️"]) + + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "😵‍💫", forAccount: activeAccount.accountId) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "😵‍💫", forAccount: activeAccount.accountId) + NCDatabaseManager.sharedInstance().increaseEmojiUsage(forEmoji: "🤷‍♂️", forAccount: activeAccount.accountId) + activeAccount = NCDatabaseManager.sharedInstance().activeAccount() + XCTAssertEqual(activeAccount.frequentlyUsedEmojis, ["🇫🇮", "🙈", "😵‍💫", "🤷‍♂️"]) + } }