diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 042901c9a..ff407b6ed 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -205,6 +205,8 @@ DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; }; DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; }; DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; }; + DB35B0B32643D821006AC73B /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DB35B0B22643D821006AC73B /* TwitterTextEditor */; }; + DB35B0B42643D821006AC73B /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DB35B0B22643D821006AC73B /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */; }; DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC242612FD7A006193C9 /* ProfileFieldView.swift */; }; DB35FC2F26130172006193C9 /* MastodonField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC2E26130172006193C9 /* MastodonField.swift */; }; @@ -437,8 +439,6 @@ DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; - DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; }; - DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DBF8AE16263293E400C9C23C /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF8AE15263293E400C9C23C /* NotificationService.swift */; }; DBF8AE1A263293E400C9C23C /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = DBF8AE13263293E400C9C23C /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DBF8AE862632992800C9C23C /* Base85 in Frameworks */ = {isa = PBXBuildFile; productRef = DBF8AE852632992800C9C23C /* Base85 */; }; @@ -548,8 +548,8 @@ dstSubfolderSpec = 10; files = ( DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */, - DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */, DB89BA0425C10FD0008580ED /* CoreDataStack.framework in Embed Frameworks */, + DB35B0B42643D821006AC73B /* TwitterTextEditor in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -1012,6 +1012,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DB35B0B32643D821006AC73B /* TwitterTextEditor in Frameworks */, DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */, 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, @@ -1024,7 +1025,6 @@ DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */, - DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */, 2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */, @@ -2417,8 +2417,8 @@ 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */, 2D939AC725EE14620076FA61 /* CropViewController */, DB9A487D2603456B008B817C /* UITextView+Placeholder */, - DBE64A8A260C49D200E6359A /* TwitterTextEditor */, DBB525072611EAC0002F1F29 /* Tabman */, + DB35B0B22643D821006AC73B /* TwitterTextEditor */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -2605,10 +2605,10 @@ 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */, 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */, DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */, - DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */, DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */, DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */, DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */, + DB35B0B12643D821006AC73B /* XCRemoteSwiftPackageReference "TwitterTextEditor" */, ); productRefGroup = DB427DD325BAA00100D1B89D /* Products */; projectDirPath = ""; @@ -3949,6 +3949,14 @@ minimumVersion = 0.1.1; }; }; + DB35B0B12643D821006AC73B /* XCRemoteSwiftPackageReference "TwitterTextEditor" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/twitter/TwitterTextEditor"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/AlamofireImage.git"; @@ -3989,14 +3997,6 @@ minimumVersion = 2.11.0; }; }; - DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/MainasuK/TwitterTextEditor"; - requirement = { - branch = "feature/input-view"; - kind = branch; - }; - }; DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/MainasuK/Base85.git"; @@ -4042,6 +4042,11 @@ package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; productName = CommonOSLog; }; + DB35B0B22643D821006AC73B /* TwitterTextEditor */ = { + isa = XCSwiftPackageProductDependency; + package = DB35B0B12643D821006AC73B /* XCRemoteSwiftPackageReference "TwitterTextEditor" */; + productName = TwitterTextEditor; + }; DB3D0FF225BAA61700EAA174 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; @@ -4072,11 +4077,6 @@ package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */; productName = Tabman; }; - DBE64A8A260C49D200E6359A /* TwitterTextEditor */ = { - isa = XCSwiftPackageProductDependency; - package = DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */; - productName = TwitterTextEditor; - }; DBF8AE852632992800C9C23C /* Base85 */ = { isa = XCSwiftPackageProductDependency; package = DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 326857269..f092f9734 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 14 + 15 Mastodon - RTL.xcscheme_^#shared#^_ @@ -32,7 +32,7 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 15 + 14 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 47136a2c7..3295adb41 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -138,11 +138,11 @@ }, { "package": "TwitterTextEditor", - "repositoryURL": "https://github.com/MainasuK/TwitterTextEditor", + "repositoryURL": "https://github.com/twitter/TwitterTextEditor", "state": { - "branch": "feature/input-view", - "revision": "1e565d13e3c26fc2bedeb418890df42f80d6e3d5", - "version": null + "branch": null, + "revision": "dfe0edc3bcb6703ee2fd0e627f95e726b63e732a", + "version": "1.1.0" } }, { diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index a18cf9216..dedcd4050 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -300,24 +300,9 @@ extension ComposeViewController { } } .store(in: &disposeBag) - - // bind text editor for custom emojis update event - viewModel.customEmojiViewModel - .compactMap { $0?.emojis } - .switchToLatest() - .sink(receiveValue: { [weak self] emojis in - guard let self = self else { return } - for emoji in emojis { - UITextChecker.learnWord(emoji.shortcode) - UITextChecker.learnWord(":" + emoji.shortcode + ":") - } - self.textEditorView()?.setNeedsUpdateTextAttributes() - }) - .store(in: &disposeBag) // bind custom emoji picker UI viewModel.customEmojiViewModel - .receive(on: DispatchQueue.main) .map { viewModel -> AnyPublisher<[Mastodon.Entity.Emoji], Never> in guard let viewModel = viewModel else { return Just([]).eraseToAnyPublisher() @@ -325,6 +310,7 @@ extension ComposeViewController { return viewModel.emojis.eraseToAnyPublisher() } .switchToLatest() + .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] emojis in guard let self = self else { return } if emojis.isEmpty { @@ -581,6 +567,7 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate { updateAttributedString attributedString: NSAttributedString, completion: @escaping (NSAttributedString?) -> Void ) { + // FIXME: needs O(1) update completion to fix profermance issue DispatchQueue.global().async { let string = attributedString.string os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update: %s", ((#file as NSString).lastPathComponent), #line, #function, string) @@ -631,11 +618,10 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate { } // emoji - let emojis = customEmojiViewModel?.emojis.value ?? [] - if !emojis.isEmpty { + if let customEmojiViewModel = customEmojiViewModel, !customEmojiViewModel.emojiDict.value.isEmpty { for match in emojiMatches { guard let name = string.substring(with: match, at: 2) else { continue } - guard let emoji = emojis.first(where: { $0.shortcode == name }) else { continue } + guard let emoji = customEmojiViewModel.emoji(shortcode: name) else { continue } os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: handle emoji: %s", ((#file as NSString).lastPathComponent), #line, #function, name) // set emoji token invisiable (without upper bounce space) diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 587b56f23..58d1ffa75 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -415,6 +415,6 @@ extension ComposeViewModel: MastodonAttachmentServiceDelegate { extension ComposeViewModel: ComposePollAttributeDelegate { func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?) { // trigger update - // pollOptionAttributes.value = pollOptionAttributes.value + pollOptionAttributes.value = pollOptionAttributes.value } } diff --git a/Mastodon/Scene/Share/View/Content/PollOptionView.swift b/Mastodon/Scene/Share/View/Content/PollOptionView.swift index 7125b691c..2a248ec3f 100644 --- a/Mastodon/Scene/Share/View/Content/PollOptionView.swift +++ b/Mastodon/Scene/Share/View/Content/PollOptionView.swift @@ -82,7 +82,7 @@ final class PollOptionView: UIView { extension PollOptionView { private func _init() { // default color in the timeline - roundedBackgroundView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + roundedBackgroundView.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color roundedBackgroundView.translatesAutoresizingMaskIntoConstraints = false addSubview(roundedBackgroundView) @@ -193,6 +193,11 @@ struct PollOptionView_Previews: PreviewProvider { PollOptionView() } .previewLayout(.fixed(width: 375, height: 100)) + UIViewPreview(width: 375) { + PollOptionView() + } + .preferredColorScheme(.dark) + .previewLayout(.fixed(width: 375, height: 100)) } } diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 34367d396..20437a8d2 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -340,9 +340,10 @@ extension StatusView { contentWarningOverlayView.translatesAutoresizingMaskIntoConstraints = false containerStackView.addSubview(contentWarningOverlayView) NSLayoutConstraint.activate([ - statusContainerStackView.topAnchor.constraint(equalTo: contentWarningOverlayView.topAnchor, constant: StatusView.contentWarningBlurRadius).priority(.defaultLow), - statusContainerStackView.leftAnchor.constraint(equalTo: contentWarningOverlayView.leftAnchor, constant: StatusView.contentWarningBlurRadius).priority(.defaultLow), - // only layout to top-left corner and draw image to fit size + statusContainerStackView.topAnchor.constraint(equalTo: contentWarningOverlayView.topAnchor, constant: StatusView.contentWarningBlurRadius).priority(.defaultHigh), + statusContainerStackView.leftAnchor.constraint(equalTo: contentWarningOverlayView.leftAnchor, constant: StatusView.contentWarningBlurRadius).priority(.defaultHigh), + contentWarningOverlayView.rightAnchor.constraint(equalTo: statusContainerStackView.rightAnchor, constant: StatusView.contentWarningBlurRadius).priority(.defaultHigh), + // only layout to top and left & right then draw image to fit size ]) // avoid overlay clip author view containerStackView.bringSubviewToFront(authorContainerStackView) diff --git a/Mastodon/Scene/Share/View/TableviewCell/PollOptionTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/PollOptionTableViewCell.swift index b067896a1..957765e10 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/PollOptionTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/PollOptionTableViewCell.swift @@ -129,14 +129,16 @@ struct PollTableViewCell_Previews: PreviewProvider { } .previewLayout(.fixed(width: 375, height: 44 + 10)) } + .background(Color(.systemBackground)) } static var previews: some View { Group { - controls.colorScheme(.light) - controls.colorScheme(.dark) + controls + .colorScheme(.light) + controls + .colorScheme(.dark) } - .background(Color.gray) } } diff --git a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift index 4fdab0bb9..a03af9bd1 100644 --- a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift +++ b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift @@ -41,7 +41,7 @@ extension EmojiService.CustomEmojiViewModel.LoadState { guard let viewModel = viewModel, let apiService = viewModel.service?.apiService, let stateMachine = stateMachine else { return } apiService.customEmoji(domain: viewModel.domain) - .receive(on: DispatchQueue.main) + // .receive(on: DispatchQueue.main) .sink { completion in switch completion { case .failure(let error): diff --git a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift index f866f4a02..d1b8494d7 100644 --- a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift +++ b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift @@ -32,10 +32,31 @@ extension EmojiService { return stateMachine }() let emojis = CurrentValueSubject<[Mastodon.Entity.Emoji], Never>([]) + let emojiDict = CurrentValueSubject<[String: [Mastodon.Entity.Emoji]], Never>([:]) + + private var learnedEmoji: Set = Set() init(domain: String, service: EmojiService) { self.domain = domain self.service = service + + emojis + .map { Dictionary(grouping: $0, by: { $0.shortcode }) } + .assign(to: \.value, on: emojiDict) + .store(in: &disposeBag) + } + + func emoji(shortcode: String) -> Mastodon.Entity.Emoji? { + if !learnedEmoji.contains(shortcode) { + learnedEmoji.insert(shortcode) + + DispatchQueue.global().async { + UITextChecker.learnWord(shortcode) + UITextChecker.learnWord(":" + shortcode + ":") + } + } + + return emojiDict.value[shortcode]?.first } }