From 00f0b2e1406658a89800b6f1318b69a5ad659883 Mon Sep 17 00:00:00 2001 From: CMK Date: Thu, 22 Jul 2021 19:34:24 +0800 Subject: [PATCH 1/5] feat: set status name animated --- Mastodon.xcodeproj/project.pbxproj | 50 +++++++---------- .../xcschemes/xcschememanagement.plist | 8 +-- .../xcshareddata/swiftpm/Package.resolved | 21 +++++--- .../Compose/ComposeStatusSection.swift | 2 +- .../Section/Status/StatusSection.swift | 31 ++++++++--- Mastodon/Extension/MetaLabel.swift | 53 +++++++++++++++++++ ...Provider+StatusTableViewCellDelegate.swift | 2 +- .../StatusProvider/StatusProviderFacade.swift | 2 +- .../ComposeStatusContentTableViewCell.swift | 2 +- .../Scene/Compose/ComposeViewController.swift | 2 +- .../Compose/ComposeViewModel+DataSource.swift | 2 +- .../CustomEmojiPickerInputViewModel.swift | 2 +- .../Compose/View/ReplicaStatusView.swift | 2 +- .../NotificationViewController.swift | 2 +- .../NotificationStatusTableViewCell.swift | 2 +- .../Header/View/ProfileHeaderView.swift | 2 +- .../Report/ReportedStatusTableviewCell.swift | 2 +- .../Scene/Share/View/Content/StatusView.swift | 34 ++++++------ .../TableviewCell/StatusTableViewCell.swift | 2 +- 19 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 Mastodon/Extension/MetaLabel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index fa8b06c3d..0f7d85276 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -179,12 +179,12 @@ DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; }; DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; }; DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; + DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23226A98F0900C3965B /* MastodonMeta */; }; + DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23426A98F0900C3965B /* MetaTextKit */; }; DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */; }; DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; }; DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; }; DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; }; - DB03F7EB268976B5007B274C /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB03F7EA268976B5007B274C /* MastodonMeta */; }; - DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */ = {isa = PBXBuildFile; productRef = DB03F7EC268976B5007B274C /* MetaTextView */; }; DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; }; DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; }; DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; }; @@ -196,6 +196,7 @@ DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */; }; DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947126A7D2D70088FB11 /* AvatarButton.swift */; }; DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; }; + DB0E91EA26A9675100BD2ACC /* MetaLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0E91E926A9675100BD2ACC /* MetaLabel.swift */; }; DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; }; DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; }; DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; }; @@ -227,8 +228,6 @@ DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; }; DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; }; - DB41ED8226A54D8A00F58330 /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED8126A54D8A00F58330 /* MastodonMeta */; }; - DB41ED8426A54D8A00F58330 /* MetaTextView in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED8326A54D8A00F58330 /* MetaTextView */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; }; DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; }; @@ -896,6 +895,7 @@ DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = ""; }; DB0C947126A7D2D70088FB11 /* AvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarButton.swift; sourceTree = ""; }; DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = ""; }; + DB0E91E926A9675100BD2ACC /* MetaLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaLabel.swift; sourceTree = ""; }; DB0F814D264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = ""; }; DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = ""; }; @@ -1257,7 +1257,6 @@ buildActionMask = 2147483647; files = ( DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, - DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */, DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */, DBC6462B26A1738900B0E31B /* MastodonUI in Frameworks */, 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, @@ -1269,14 +1268,15 @@ DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */, DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, + DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */, DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */, DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */, DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */, 2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */, - DB03F7EB268976B5007B274C /* MastodonMeta in Frameworks */, DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */, + DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1329,8 +1329,6 @@ DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */, DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */, DBC6463726A195DB00B0E31B /* CoreDataStack.framework in Frameworks */, - DB41ED8426A54D8A00F58330 /* MetaTextView in Frameworks */, - DB41ED8226A54D8A00F58330 /* MastodonMeta in Frameworks */, DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */, DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */, DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */, @@ -2502,6 +2500,7 @@ DB6C8C0525F0921200AAA452 /* MastodonSDK */, DB44384E25E8C1FA008912A2 /* CALayer.swift */, 2DF123A625C3B0210020F248 /* ActiveLabel.swift */, + DB0E91E926A9675100BD2ACC /* MetaLabel.swift */, DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */, DB0140CE25C42AEE00F9F3CF /* OSLog.swift */, DB68A06225E905E000CFDF14 /* UIApplication.swift */, @@ -2999,9 +2998,9 @@ DBAC649D267DFE43007FE9FD /* DiffableDataSources */, DBAC64A0267E6D02007FE9FD /* Fuzi */, DBF7A0FB26830C33004176A2 /* FPSIndicator */, - DB03F7EA268976B5007B274C /* MastodonMeta */, - DB03F7EC268976B5007B274C /* MetaTextView */, DBC6462A26A1738900B0E31B /* MastodonUI */, + DB01E23226A98F0900C3965B /* MastodonMeta */, + DB01E23426A98F0900C3965B /* MetaTextKit */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -3127,8 +3126,6 @@ DBBC24A926A5301B00398BB9 /* MastodonSDK */, DBBC24B726A5421800398BB9 /* CommonOSLog */, DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */, - DB41ED8126A54D8A00F58330 /* MastodonMeta */, - DB41ED8326A54D8A00F58330 /* MetaTextView */, DB0C946426A6FD4D0088FB11 /* AlamofireImage */, ); productName = ShareActionExtension; @@ -3226,7 +3223,7 @@ DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */, DBF7A0FA26830C33004176A2 /* XCRemoteSwiftPackageReference "FPSIndicator" */, DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */, - DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */, + DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */, ); productRefGroup = DB427DD325BAA00100D1B89D /* Products */; projectDirPath = ""; @@ -3752,6 +3749,7 @@ DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */, DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */, DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */, + DB0E91EA26A9675100BD2ACC /* MetaLabel.swift in Sources */, DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */, DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */, 2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */, @@ -5349,12 +5347,12 @@ minimumVersion = 0.1.1; }; }; - DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */ = { + DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/TwidereProject/MetaTextView.git"; + repositoryURL = "https://github.com/TwidereProject/MetaTextKit.git"; requirement = { kind = exactVersion; - version = 1.4.0; + version = 2.0.0; }; }; DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = { @@ -5474,15 +5472,15 @@ package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; productName = CommonOSLog; }; - DB03F7EA268976B5007B274C /* MastodonMeta */ = { + DB01E23226A98F0900C3965B /* MastodonMeta */ = { isa = XCSwiftPackageProductDependency; - package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; + package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */; productName = MastodonMeta; }; - DB03F7EC268976B5007B274C /* MetaTextView */ = { + DB01E23426A98F0900C3965B /* MetaTextKit */ = { isa = XCSwiftPackageProductDependency; - package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; - productName = MetaTextView; + package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */; + productName = MetaTextKit; }; DB0C946426A6FD4D0088FB11 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; @@ -5494,16 +5492,6 @@ package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; productName = AlamofireImage; }; - DB41ED8126A54D8A00F58330 /* MastodonMeta */ = { - isa = XCSwiftPackageProductDependency; - package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; - productName = MastodonMeta; - }; - DB41ED8326A54D8A00F58330 /* MetaTextView */ = { - isa = XCSwiftPackageProductDependency; - package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; - productName = MetaTextView; - }; DB68050F2637D0F800430867 /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index fd09596bc..1220300f7 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 - 22 + 24 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -22,7 +22,7 @@ Mastodon - RTL.xcscheme_^#shared#^_ orderHint - 3 + 4 Mastodon - Release.xcscheme_^#shared#^_ @@ -37,12 +37,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 23 + 22 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 21 + 23 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 55f7d7113..45f1697c9 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/Alamofire/Alamofire.git", "state": { "branch": null, - "revision": "4d19ad82f80cc71ff829b941ded114c56f4f604c", - "version": "5.4.2" + "revision": "f96b619bcb2383b43d898402283924b80e2c4bae", + "version": "5.4.3" } }, { @@ -110,12 +110,12 @@ } }, { - "package": "MetaTextView", - "repositoryURL": "https://github.com/TwidereProject/MetaTextView.git", + "package": "MetaTextKit", + "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", "state": { "branch": null, - "revision": "4c16bc639652a7e1bff4f75e1eba2fcf40213974", - "version": "1.4.0" + "revision": "44fc5111269d9862369348870835e17907062115", + "version": "2.0.0" } }, { @@ -145,6 +145,15 @@ "version": "3.6.2" } }, + { + "package": "SDWebImage", + "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", + "state": { + "branch": null, + "revision": "76dd4b49110b8624317fc128e7fa0d8a252018bc", + "version": "5.11.1" + } + }, { "package": "swift-nio", "repositoryURL": "https://github.com/apple/swift-nio.git", diff --git a/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift index 6a9dd2b3b..bacc5097c 100644 --- a/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift +++ b/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift @@ -9,7 +9,7 @@ import UIKit import Combine import CoreData import CoreDataStack -import MetaTextView +import MetaTextKit import MastodonMeta import AlamofireImage diff --git a/Mastodon/Diffiable/Section/Status/StatusSection.swift b/Mastodon/Diffiable/Section/Status/StatusSection.swift index 18406da66..9bb34466e 100644 --- a/Mastodon/Diffiable/Section/Status/StatusSection.swift +++ b/Mastodon/Diffiable/Section/Status/StatusSection.swift @@ -113,7 +113,7 @@ extension StatusSection { accessibilityViews.append(cell.statusView.headerInfoLabel) } accessibilityViews.append(contentsOf: [ - cell.statusView.nameLabel, + cell.statusView.nameMetaLabel, cell.statusView.dateLabel, cell.statusView.contentMetaText.textView, ]) @@ -171,7 +171,7 @@ extension StatusSection { cell.statusView.contentMetaText.textView.isAccessibilityElement = false var accessibilityElements: [Any] = [] accessibilityElements.append(cell.statusView.avatarView) - accessibilityElements.append(cell.statusView.nameLabel) + accessibilityElements.append(cell.statusView.nameMetaLabel) accessibilityElements.append(cell.statusView.dateLabel) // TODO: a11y accessibilityElements.append(cell.statusView.contentMetaText.textView) @@ -652,7 +652,13 @@ extension StatusSection { return L10n.Common.Controls.Status.userReblogged(name) }() // sync set display name to avoid layout issue - cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.author.emojiDict) + do { + let mastodonContent = MastodonContent(content: headerText, emojis: status.author.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.statusView.headerInfoLabel.configure(content: metaContent) + } catch { + cell.statusView.headerInfoLabel.reset() + } cell.statusView.headerInfoLabel.accessibilityLabel = headerText cell.statusView.headerInfoLabel.isAccessibilityElement = true } else if status.inReplyToID != nil { @@ -666,7 +672,13 @@ extension StatusSection { let name = author.displayName.isEmpty ? author.username : author.displayName return L10n.Common.Controls.Status.userRepliedTo(name) }() - cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.replyTo?.author.emojiDict ?? [:]) + do { + let mastodonContent = MastodonContent(content: headerText, emojis: status.replyTo?.author.emojiMeta ?? [:]) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.statusView.headerInfoLabel.configure(content: metaContent) + } catch { + cell.statusView.headerInfoLabel.reset() + } cell.statusView.headerInfoLabel.accessibilityLabel = headerText cell.statusView.headerInfoLabel.isAccessibilityElement = status.replyTo != nil } else { @@ -682,8 +694,15 @@ extension StatusSection { // name let author = (status.reblog ?? status).author let nameContent = author.displayNameWithFallback - cell.statusView.nameLabel.configure(content: nameContent, emojiDict: author.emojiDict) - cell.statusView.nameLabel.accessibilityLabel = nameContent + do { + let mastodonContent = MastodonContent(content: nameContent, emojis: author.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.statusView.nameMetaLabel.configure(content: metaContent) + cell.statusView.nameMetaLabel.accessibilityLabel = metaContent.trimmed + } catch { + cell.statusView.nameMetaLabel.reset() + cell.statusView.nameMetaLabel.accessibilityLabel = "" + } // username cell.statusView.usernameLabel.text = "@" + author.acct // avatar diff --git a/Mastodon/Extension/MetaLabel.swift b/Mastodon/Extension/MetaLabel.swift new file mode 100644 index 000000000..67200874b --- /dev/null +++ b/Mastodon/Extension/MetaLabel.swift @@ -0,0 +1,53 @@ +// +// MetaText.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-22. +// + +import UIKit +import MetaTextKit + +extension MetaLabel { + enum Style { + case statusHeader + case statusName +// case profileFieldName +// case profileFieldValue + } + + convenience init(style: Style) { + self.init() + + layer.masksToBounds = true + textContainer.lineBreakMode = .byTruncatingTail + + let font: UIFont + let textColor: UIColor + + switch style { + case .statusHeader: + font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium), maximumPointSize: 17) + textColor = Asset.Colors.Label.secondary.color + + case .statusName: + font = .systemFont(ofSize: 17, weight: .semibold) + textColor = Asset.Colors.Label.primary.color +// case .profileFieldName: +// font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 20) +// textColor = Asset.Colors.Label.primary.color +// numberOfLines = 1 +// case .profileFieldValue: +// font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 20) +// textColor = Asset.Colors.Label.primary.color +// numberOfLines = 1 + } + + self.font = font + self.textColor = textColor + + textAttributes = [ + .font: font, + .foregroundColor: textColor + ] + }} diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift index a752f2180..544de8893 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift @@ -13,7 +13,7 @@ import CoreDataStack import MastodonSDK import ActiveLabel import Meta -import MetaTextView +import MetaTextKit // MARK: - StatusViewDelegate extension StatusTableViewCellDelegate where Self: StatusProvider { diff --git a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift index 40f0f6bb8..e5bb15a80 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift @@ -13,7 +13,7 @@ import CoreDataStack import MastodonSDK import ActiveLabel import Meta -import MetaTextView +import MetaTextKit #if ASDK import AsyncDisplayKit diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift index 7dbda5b36..d970b6734 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift @@ -8,7 +8,7 @@ import os.log import UIKit import Combine -import MetaTextView +import MetaTextKit import UITextView_Placeholder final class ComposeStatusContentTableViewCell: UITableViewCell { diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index 4ce020837..156680496 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -10,7 +10,7 @@ import UIKit import Combine import PhotosUI import MastodonSDK -import MetaTextView +import MetaTextKit import MastodonMeta import Meta import MastodonUI diff --git a/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift index 4b4e80c52..2642808d1 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift @@ -12,7 +12,7 @@ import CoreDataStack import TwitterTextEditor import MastodonSDK import MastodonMeta -import MetaTextView +import MetaTextKit extension ComposeViewModel { diff --git a/Mastodon/Scene/Compose/View/CustomEmojiPickerInputViewModel.swift b/Mastodon/Scene/Compose/View/CustomEmojiPickerInputViewModel.swift index d421759b1..d258ff7b8 100644 --- a/Mastodon/Scene/Compose/View/CustomEmojiPickerInputViewModel.swift +++ b/Mastodon/Scene/Compose/View/CustomEmojiPickerInputViewModel.swift @@ -7,7 +7,7 @@ import UIKit import Combine -import MetaTextView +import MetaTextKit final class CustomEmojiPickerInputViewModel { diff --git a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift index 609e4bcc6..8e6d46878 100644 --- a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift +++ b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift @@ -9,7 +9,7 @@ import os.log import UIKit import ActiveLabel import FLAnimatedImage -import MetaTextView +import MetaTextKit final class ReplicaStatusView: UIView { diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 658c5fcab..5528adf68 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -14,7 +14,7 @@ import OSLog import UIKit import ActiveLabel import Meta -import MetaTextView +import MetaTextKit final class NotificationViewController: UIViewController, NeedsDependency { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index 684d9187c..6afc61e9a 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -11,7 +11,7 @@ import Foundation import CoreDataStack import UIKit import ActiveLabel -import MetaTextView +import MetaTextKit import Meta import FLAnimatedImage diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index 95a91b994..043e1b665 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -11,7 +11,7 @@ import Combine import ActiveLabel import TwitterTextEditor import FLAnimatedImage -import MetaTextView +import MetaTextKit protocol ProfileHeaderViewDelegate: AnyObject { func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) diff --git a/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift b/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift index 639167684..32d096a33 100644 --- a/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift +++ b/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift @@ -13,7 +13,7 @@ import CoreData import CoreDataStack import ActiveLabel import Meta -import MetaTextView +import MetaTextKit final class ReportedStatusTableViewCell: UITableViewCell, StatusCell { diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index a12a7bd28..0964ee3ba 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -12,7 +12,7 @@ import AVKit import ActiveLabel import AlamofireImage import FLAnimatedImage -import MetaTextView +import MetaTextKit import Meta import MastodonSDK @@ -81,12 +81,7 @@ final class StatusView: UIView { return label }() - let headerInfoLabel: ActiveLabel = { - let label = ActiveLabel(style: .statusHeader) - label.text = "Bob reblogged" - label.layer.masksToBounds = false - return label - }() + let headerInfoLabel = MetaLabel(style: .statusHeader) let avatarView: UIView = { let view = UIView() @@ -98,8 +93,8 @@ final class StatusView: UIView { let avatarButton = AvatarButton() let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton() - let nameLabel: ActiveLabel = { - let label = ActiveLabel(style: .statusName) + let nameMetaLabel: MetaLabel = { + let label = MetaLabel(style: .statusName) return label }() @@ -343,24 +338,27 @@ extension StatusView { titleContainerStackView.axis = .horizontal titleContainerStackView.alignment = .center titleContainerStackView.spacing = 4 - nameLabel.translatesAutoresizingMaskIntoConstraints = false - titleContainerStackView.addArrangedSubview(nameLabel) + nameMetaLabel.translatesAutoresizingMaskIntoConstraints = false + titleContainerStackView.addArrangedSubview(nameMetaLabel) NSLayoutConstraint.activate([ - nameLabel.heightAnchor.constraint(equalToConstant: 22).priority(.defaultHigh), + nameMetaLabel.heightAnchor.constraint(equalToConstant: 22).priority(.defaultHigh), ]) - titleContainerStackView.alignment = .firstBaseline titleContainerStackView.addArrangedSubview(nameTrialingDotLabel) titleContainerStackView.addArrangedSubview(dateLabel) - titleContainerStackView.addArrangedSubview(UIView()) // padding + let padding = UIView() + padding.translatesAutoresizingMaskIntoConstraints = false + titleContainerStackView.addArrangedSubview(padding) // padding titleContainerStackView.addArrangedSubview(visibilityImageView) - nameLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) + nameMetaLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) nameTrialingDotLabel.setContentHuggingPriority(.defaultHigh + 2, for: .horizontal) nameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal) dateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) - dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal) - visibilityImageView.setContentHuggingPriority(.required - 1, for: .horizontal) + dateLabel.setContentCompressionResistancePriority(.required - 10, for: .horizontal) + padding.setContentHuggingPriority(.defaultLow, for: .horizontal) + padding.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + visibilityImageView.setContentHuggingPriority(.required - 9, for: .horizontal) + visibilityImageView.setContentCompressionResistancePriority(.required - 9, for: .horizontal) visibilityImageView.setContentHuggingPriority(.required - 1, for: .vertical) - visibilityImageView.setContentCompressionResistancePriority(.required - 1, for: .horizontal) // subtitle container: [username] let subtitleContainerStackView = UIStackView() diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index 508d928e1..3a186f00b 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -13,7 +13,7 @@ import CoreData import CoreDataStack import ActiveLabel import Meta -import MetaTextView +import MetaTextKit protocol StatusTableViewCellDelegate: AnyObject { var context: AppContext! { get } From 9577512ed5bc611a4db9bf20882f30b54cd98d19 Mon Sep 17 00:00:00 2001 From: CMK Date: Thu, 22 Jul 2021 19:36:41 +0800 Subject: [PATCH 2/5] chore: update version to 0.9.3 (44) --- Mastodon.xcodeproj/project.pbxproj | 48 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 0f7d85276..7d42a6e85 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -4306,7 +4306,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4314,7 +4314,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4333,7 +4333,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4341,7 +4341,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4598,7 +4598,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4606,7 +4606,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4622,7 +4622,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4630,7 +4630,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4646,7 +4646,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4654,7 +4654,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4670,7 +4670,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4678,7 +4678,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4759,7 +4759,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4767,7 +4767,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4874,7 +4874,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4882,7 +4882,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4993,7 +4993,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5001,7 +5001,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -5108,7 +5108,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5116,7 +5116,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -5162,7 +5162,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5170,7 +5170,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -5185,7 +5185,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5193,7 +5193,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.2; + MARKETING_VERSION = 0.9.3; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; From cfc598752811a909f77c228660493169aa91fd33 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 23 Jul 2021 19:10:27 +0800 Subject: [PATCH 3/5] feat: add APNG supports for more label --- Mastodon.xcodeproj/project.pbxproj | 46 +-- .../xcschemes/xcschememanagement.plist | 8 +- .../xcshareddata/swiftpm/Package.resolved | 27 -- .../Diffiable/Item/ComposeStatusItem.swift | 5 +- .../Diffiable/Item/ProfileFieldItem.swift | 3 +- .../Compose/ComposeStatusSection.swift | 15 +- .../Compose/CustomEmojiPickerSection.swift | 7 +- .../Section/ProfileFieldSection.swift | 49 ++- .../Section/Status/NotificationSection.swift | 11 +- Mastodon/Extension/ActiveLabel.swift | 229 ++++-------- Mastodon/Extension/CoreDataStack/Emojis.swift | 9 - .../MastodonSDK/Mastodon+Entity+Account.swift | 11 + Mastodon/Extension/MetaLabel.swift | 69 +++- Mastodon/Helper/MastodonField.swift | 63 ---- .../MastodonStatusContent+Appearance.swift | 17 - .../MastodonStatusContent+ParseResult.swift | 108 ------ Mastodon/Helper/MastodonStatusContent.swift | 332 ------------------ .../StatusProvider+StatusNodeDelegate.swift | 5 +- ...Provider+StatusTableViewCellDelegate.swift | 5 - .../StatusProvider/StatusProviderFacade.swift | 55 --- .../ComposeStatusContentTableViewCell.swift | 5 +- ...tomEmojiPickerItemCollectionViewCell.swift | 6 +- .../Compose/ComposeViewModel+DataSource.swift | 26 +- Mastodon/Scene/Compose/ComposeViewModel.swift | 2 +- .../Compose/View/ReplicaStatusView.swift | 13 +- .../HashtagTimelineViewController.swift | 6 +- .../HashtagTimelineViewModel+Diffable.swift | 2 +- .../HashtagTimelineViewModel.swift | 2 +- ...cHomeTimelineViewController+Provider.swift | 6 + .../AsyncHomeTimelineViewController.swift | 6 +- .../AsyncHomeTimelineViewModel+Diffable.swift | 2 +- .../AsyncHomeTimelineViewModel.swift | 3 +- .../HomeTimelineViewController.swift | 2 +- .../HomeTimelineViewModel+Diffable.swift | 2 +- .../HomeTimeline/HomeTimelineViewModel.swift | 3 +- .../NotificationViewController.swift | 8 +- .../Notification/NotificationViewModel.swift | 2 +- .../NotificationStatusTableViewCell.swift | 15 +- .../Favorite/FavoriteViewController.swift | 2 +- .../Header/ProfileHeaderViewController.swift | 76 ++-- .../Header/ProfileHeaderViewModel.swift | 9 +- ...ofileFieldAddEntryCollectionViewCell.swift | 8 +- .../View/ProfileFieldCollectionViewCell.swift | 16 +- .../Header/View/ProfileFieldView.swift | 32 +- .../Header/View/ProfileHeaderView.swift | 104 +++--- .../Scene/Profile/ProfileViewController.swift | 54 +-- Mastodon/Scene/Profile/ProfileViewModel.swift | 7 +- .../Report/ReportedStatusTableviewCell.swift | 4 - ...hRecommendAccountsCollectionViewCell.swift | 21 +- .../SearchResultTableViewCell.swift | 29 +- .../Settings/SettingsViewController.swift | 41 ++- ...ubleTitleLabelNavigationBarTitleView.swift | 24 +- .../Scene/Share/View/Content/StatusView.swift | 17 +- .../Share/View/Node/Status/StatusNode.swift | 78 ++-- .../TableviewCell/StatusTableViewCell.swift | 6 - .../SuggestionAccountTableViewCell.swift | 22 +- .../Scene/Thread/ThreadViewController.swift | 32 +- Mastodon/Scene/Thread/ThreadViewModel.swift | 11 +- .../Service/StatusContentCacheService.swift | 77 ---- Mastodon/State/AppContext.swift | 1 - 60 files changed, 562 insertions(+), 1294 deletions(-) delete mode 100644 Mastodon/Helper/MastodonField.swift delete mode 100644 Mastodon/Helper/MastodonStatusContent+Appearance.swift delete mode 100644 Mastodon/Helper/MastodonStatusContent+ParseResult.swift delete mode 100755 Mastodon/Helper/MastodonStatusContent.swift delete mode 100644 Mastodon/Service/StatusContentCacheService.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 7d42a6e85..d42733555 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -65,7 +65,6 @@ 2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1FD25CD481700561493 /* StatusProvider.swift */; }; 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; }; 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */; }; - 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; }; 2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; }; 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */; }; 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; }; @@ -309,7 +308,6 @@ DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DB6804922637CD8700430867 /* AppName.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804912637CD8700430867 /* AppName.swift */; }; DB6804A52637CDCC00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; - DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; DB6804D12637CE4700430867 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804D02637CE4700430867 /* UserDefaults.swift */; }; DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804FC2637CFEC00430867 /* AppSecret.swift */; }; DB6805102637D0F800430867 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB68050F2637D0F800430867 /* KeychainAccess */; }; @@ -442,7 +440,6 @@ DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; }; DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; }; DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */; }; - DBAEDE61267B342D00D25FF5 /* StatusContentCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */; }; DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; }; DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; }; DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; }; @@ -479,11 +476,7 @@ DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; }; DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */; }; DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; }; - DBBC24DD26A54BCB00398BB9 /* MastodonStatusContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */; }; DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */; }; - DBBC24DF26A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */; }; - DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */; }; - DBBC24E126A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */; }; DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; }; DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; }; DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; }; @@ -930,6 +923,7 @@ DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + DB3F693926AA97BD00C883AB /* MetaTextKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = MetaTextKit; path = ../MetaTextKit; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB427DD525BAA00100D1B89D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DB427DD725BAA00100D1B89D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -1143,7 +1137,6 @@ DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = ""; }; DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = ""; }; DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = ""; }; - DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentCacheService.swift; sourceTree = ""; }; DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = ""; }; DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = ""; }; DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = ""; }; @@ -1168,11 +1161,7 @@ DBBC24C326A544B900398BB9 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = ""; }; DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; }; - DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonStatusContent.swift; sourceTree = ""; }; DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; - DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+ParseResult.swift"; sourceTree = ""; }; - DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonField.swift; sourceTree = ""; }; - DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+Appearance.swift"; sourceTree = ""; }; DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; }; DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = ""; }; DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTableViewCell.swift; sourceTree = ""; }; @@ -1259,7 +1248,6 @@ DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */, DBC6462B26A1738900B0E31B /* MastodonUI in Frameworks */, - 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */, 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */, DBB525082611EAC0002F1F29 /* Tabman in Frameworks */, @@ -1274,7 +1262,6 @@ DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */, 2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, - DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */, DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */, DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */, ); @@ -1600,7 +1587,6 @@ DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */, DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */, DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */, - DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */, DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */, ); path = Service; @@ -1974,6 +1960,7 @@ children = ( DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */, DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */, + DB3F693926AA97BD00C883AB /* MetaTextKit */, DB3D0FED25BAA42200EAA174 /* MastodonSDK */, DB427DD425BAA00100D1B89D /* Mastodon */, DB427DEB25BAA00100D1B89D /* MastodonTests */, @@ -2771,11 +2758,7 @@ isa = PBXGroup; children = ( DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */, - DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */, DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */, - DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */, - DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */, - DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */, DBFEF07626A691FB006D7ED1 /* MastodonAuthenticationBox.swift */, ); path = Helper; @@ -2988,7 +2971,6 @@ DB3D0FF225BAA61700EAA174 /* AlamofireImage */, 5D526FE125BE9AC400460CB9 /* MastodonSDK */, 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */, - 2D42FF6025C8177C004A627A /* ActiveLabel */, DB0140BC25C40D7500F9F3CF /* CommonOSLog */, 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */, 2D939AC725EE14620076FA61 /* CropViewController */, @@ -3163,7 +3145,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1240; + LastUpgradeCheck = 1250; TargetAttributes = { DB427DD125BAA00100D1B89D = { CreatedOnToolsVersion = 12.4; @@ -3210,7 +3192,6 @@ packageReferences = ( DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */, 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */, - 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */, DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */, 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */, 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */, @@ -3552,7 +3533,6 @@ DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */, DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */, 2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */, - DBAEDE61267B342D00D25FF5 /* StatusContentCacheService.swift in Sources */, DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */, 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */, DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */, @@ -3712,7 +3692,6 @@ 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */, 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */, 5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */, - DBBC24DF26A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift in Sources */, 2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */, DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */, DBAC6488267D388B007FE9FD /* ASTableNode.swift in Sources */, @@ -3726,7 +3705,6 @@ DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */, 2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */, 5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */, - DBBC24E126A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift in Sources */, DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */, 2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */, DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */, @@ -3786,7 +3764,6 @@ DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */, DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */, DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */, - DBBC24DD26A54BCB00398BB9 /* MastodonStatusContent.swift in Sources */, 2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */, DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */, DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */, @@ -3838,7 +3815,6 @@ DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */, DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */, DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */, - DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */, DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */, DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */, DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */, @@ -4746,6 +4722,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ASDK; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = "ASDK - Release"; @@ -5307,14 +5284,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift"; - requirement = { - kind = exactVersion; - version = 5.0.3; - }; - }; 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/vtourraine/ThirdPartyMailer.git"; @@ -5352,7 +5321,7 @@ repositoryURL = "https://github.com/TwidereProject/MetaTextKit.git"; requirement = { kind = exactVersion; - version = 2.0.0; + version = 2.1.0; }; }; DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = { @@ -5438,11 +5407,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 2D42FF6025C8177C004A627A /* ActiveLabel */ = { - isa = XCSwiftPackageProductDependency; - package = 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */; - productName = ActiveLabel; - }; 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */ = { isa = XCSwiftPackageProductDependency; package = 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 1220300f7..d551bebb6 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 - 24 + 25 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -22,7 +22,7 @@ Mastodon - RTL.xcscheme_^#shared#^_ orderHint - 4 + 3 Mastodon - Release.xcscheme_^#shared#^_ @@ -37,12 +37,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 22 + 23 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 23 + 24 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 45f1697c9..56f675a6d 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "ActiveLabel", - "repositoryURL": "https://github.com/TwidereProject/ActiveLabel.swift", - "state": { - "branch": null, - "revision": "d503eb3bfabc54a70139618ab2ba09ebb8c09672", - "version": "5.0.3" - } - }, { "package": "Alamofire", "repositoryURL": "https://github.com/Alamofire/Alamofire.git", @@ -100,24 +91,6 @@ "version": "4.2.2" } }, - { - "package": "Kingfisher", - "repositoryURL": "https://github.com/onevcat/Kingfisher.git", - "state": { - "branch": null, - "revision": "44450a8f564d7c0165f736ba2250649ff8d3e556", - "version": "6.3.0" - } - }, - { - "package": "MetaTextKit", - "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", - "state": { - "branch": null, - "revision": "44fc5111269d9862369348870835e17907062115", - "version": "2.0.0" - } - }, { "package": "Nuke", "repositoryURL": "https://github.com/kean/Nuke.git", diff --git a/Mastodon/Diffiable/Item/ComposeStatusItem.swift b/Mastodon/Diffiable/Item/ComposeStatusItem.swift index 7c916b166..c2c3f46d8 100644 --- a/Mastodon/Diffiable/Item/ComposeStatusItem.swift +++ b/Mastodon/Diffiable/Item/ComposeStatusItem.swift @@ -8,6 +8,7 @@ import Foundation import Combine import CoreData +import MastodonMeta /// Note: update Equatable when change case enum ComposeStatusItem { @@ -25,7 +26,7 @@ extension ComposeStatusItem { let avatarURL = CurrentValueSubject(nil) let displayName = CurrentValueSubject(nil) - let emojiDict = CurrentValueSubject([:]) + let emojiMeta = CurrentValueSubject([:]) let username = CurrentValueSubject(nil) let composeContent = CurrentValueSubject(nil) @@ -35,7 +36,7 @@ extension ComposeStatusItem { static func == (lhs: ComposeStatusAttribute, rhs: ComposeStatusAttribute) -> Bool { return lhs.avatarURL.value == rhs.avatarURL.value && lhs.displayName.value == rhs.displayName.value && - lhs.emojiDict.value == rhs.emojiDict.value && + lhs.emojiMeta.value == rhs.emojiMeta.value && lhs.username.value == rhs.username.value && lhs.composeContent.value == rhs.composeContent.value && lhs.isContentWarningComposing.value == rhs.isContentWarningComposing.value && diff --git a/Mastodon/Diffiable/Item/ProfileFieldItem.swift b/Mastodon/Diffiable/Item/ProfileFieldItem.swift index 4c6b37da8..781da1851 100644 --- a/Mastodon/Diffiable/Item/ProfileFieldItem.swift +++ b/Mastodon/Diffiable/Item/ProfileFieldItem.swift @@ -8,6 +8,7 @@ import Foundation import Combine import MastodonSDK +import MastodonMeta enum ProfileFieldItem { case field(field: FieldValue, attribute: FieldItemAttribute) @@ -56,7 +57,7 @@ extension ProfileFieldItem { extension ProfileFieldItem { class FieldItemAttribute: Equatable, ProfileFieldListSeparatorLineConfigurable { - let emojiDict = CurrentValueSubject([:]) + let emojiMeta = CurrentValueSubject([:]) var isEditing = false var isLast = false diff --git a/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift index bacc5097c..45b0656f4 100644 --- a/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift +++ b/Mastodon/Diffiable/Section/Compose/ComposeStatusSection.swift @@ -45,12 +45,19 @@ extension ComposeStatusSection { // set display name and username Publishers.CombineLatest3( attribute.displayName, - attribute.emojiDict, - attribute.username.eraseToAnyPublisher() + attribute.emojiMeta, + attribute.username ) .receive(on: DispatchQueue.main) - .sink { displayName, emojiDict, username in - cell.statusView.nameLabel.configure(content: displayName ?? " ", emojiDict: emojiDict) + .sink { displayName, emojiMeta, username in + do { + let mastodonContent = MastodonContent(content: displayName ?? " ", emojis: emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.statusView.nameLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: " ") + cell.statusView.nameLabel.configure(content: metaContent) + } cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " " } .store(in: &cell.disposeBag) diff --git a/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift b/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift index 70de18c6b..251b52f3a 100644 --- a/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift +++ b/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift @@ -25,7 +25,12 @@ extension CustomEmojiPickerSection { .af.imageRounded(withCornerRadius: 4) let url = URL(string: attribute.emoji.url) - cell.emojiImageView.setImage(url: url, placeholder: placeholder, scaleToSize: CustomEmojiPickerItemCollectionViewCell.itemSize) + cell.emojiImageView.sd_setImage( + with: url, + placeholderImage: placeholder, + options: [], + context: nil + ) cell.accessibilityLabel = attribute.emoji.shortcode return cell } diff --git a/Mastodon/Diffiable/Section/ProfileFieldSection.swift b/Mastodon/Diffiable/Section/ProfileFieldSection.swift index 1c8546eb3..e96a16e9e 100644 --- a/Mastodon/Diffiable/Section/ProfileFieldSection.swift +++ b/Mastodon/Diffiable/Section/ProfileFieldSection.swift @@ -8,6 +8,7 @@ import os import UIKit import Combine +import MastodonMeta enum ProfileFieldSection: Equatable, Hashable { case main @@ -29,32 +30,60 @@ extension ProfileFieldSection { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ProfileFieldCollectionViewCell.self), for: indexPath) as! ProfileFieldCollectionViewCell // set key - cell.fieldView.titleActiveLabel.configure(field: field.name.value, emojiDict: attribute.emojiDict.value) + do { + let mastodonContent = MastodonContent(content: field.name.value, emojis: attribute.emojiMeta.value) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.fieldView.titleMetaLabel.configure(content: metaContent) + } catch { + let content = PlaintextMetaContent(string: field.name.value) + cell.fieldView.titleMetaLabel.configure(content: content) + } cell.fieldView.titleTextField.text = field.name.value Publishers.CombineLatest( field.name.removeDuplicates(), - attribute.emojiDict.removeDuplicates() + attribute.emojiMeta.removeDuplicates() ) .receive(on: RunLoop.main) - .sink { [weak cell] name, emojiDict in + .sink { [weak cell] name, emojiMeta in guard let cell = cell else { return } - cell.fieldView.titleActiveLabel.configure(field: name, emojiDict: emojiDict) + do { + let mastodonContent = MastodonContent(content: name, emojis: emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.fieldView.titleMetaLabel.configure(content: metaContent) + } catch { + let content = PlaintextMetaContent(string: name) + cell.fieldView.titleMetaLabel.configure(content: content) + } // only bind label. The text field should only set once } .store(in: &cell.disposeBag) // set value - cell.fieldView.valueActiveLabel.configure(field: field.value.value, emojiDict: attribute.emojiDict.value) + do { + let mastodonContent = MastodonContent(content: field.value.value, emojis: attribute.emojiMeta.value) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.fieldView.valueMetaLabel.configure(content: metaContent) + } catch { + let content = PlaintextMetaContent(string: field.value.value) + cell.fieldView.valueMetaLabel.configure(content: content) + } cell.fieldView.valueTextField.text = field.value.value Publishers.CombineLatest( field.value.removeDuplicates(), - attribute.emojiDict.removeDuplicates() + attribute.emojiMeta.removeDuplicates() ) .receive(on: RunLoop.main) - .sink { [weak cell] value, emojiDict in + .sink { [weak cell] value, emojiMeta in guard let cell = cell else { return } - cell.fieldView.valueActiveLabel.configure(field: value, emojiDict: emojiDict) + do { + let mastodonContent = MastodonContent(content: value, emojis: emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.fieldView.valueMetaLabel.configure(content: metaContent) + } catch { + let content = PlaintextMetaContent(string: value) + cell.fieldView.valueMetaLabel.configure(content: content) + } // only bind label. The text field should only set once } .store(in: &cell.disposeBag) @@ -76,8 +105,8 @@ extension ProfileFieldSection { // setup editing state cell.fieldView.titleTextField.isHidden = !attribute.isEditing cell.fieldView.valueTextField.isHidden = !attribute.isEditing - cell.fieldView.titleActiveLabel.isHidden = attribute.isEditing - cell.fieldView.valueActiveLabel.isHidden = attribute.isEditing + cell.fieldView.titleMetaLabel.isHidden = attribute.isEditing + cell.fieldView.valueMetaLabel.isHidden = attribute.isEditing // set control hidden let isHidden = !attribute.isEditing diff --git a/Mastodon/Diffiable/Section/Status/NotificationSection.swift b/Mastodon/Diffiable/Section/Status/NotificationSection.swift index 01229235b..3113a4462 100644 --- a/Mastodon/Diffiable/Section/Status/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/Status/NotificationSection.swift @@ -11,6 +11,8 @@ import CoreDataStack import Foundation import MastodonSDK import UIKit +import MetaTextKit +import MastodonMeta enum NotificationSection: Equatable, Hashable { case main @@ -66,7 +68,14 @@ extension NotificationSection { .store(in: &cell.disposeBag) // configure author name, notification description, timestamp - cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict) + do { + let mastodonContent = MastodonContent(content: notification.account.displayNameWithFallback, emojis: notification.account.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.nameLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: notification.account.displayNameWithFallback) + cell.nameLabel.configure(content: metaContent) + } let createAt = notification.createAt let actionText = notification.notificationType.actionText cell.actionLabel.text = actionText + " · " + createAt.timeAgoSinceNow diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift index 584888195..4b23d4eb3 100644 --- a/Mastodon/Extension/ActiveLabel.swift +++ b/Mastodon/Extension/ActiveLabel.swift @@ -1,173 +1,66 @@ +//extension ActiveEntity { // -// ActiveLabel.swift -// Mastodon +// var accessibilityLabelDescription: String { +// switch self.type { +// case .email: return L10n.Common.Controls.Status.Tag.email +// case .hashtag: return L10n.Common.Controls.Status.Tag.hashtag +// case .mention: return L10n.Common.Controls.Status.Tag.mention +// case .url: return L10n.Common.Controls.Status.Tag.url +// case .emoji: return L10n.Common.Controls.Status.Tag.emoji +// } +// } // -// Created by sxiaojian on 2021/1/29. +// var accessibilityValueDescription: String { +// switch self.type { +// case .email(let text, _): return text +// case .hashtag(let text, _): return text +// case .mention(let text, _): return text +// case .url(_, let trimmed, _, _): return trimmed +// case .emoji(let text, _, _): return text +// } +// } // +// func accessibilityElement(in accessibilityContainer: Any) -> ActiveLabelAccessibilityElement? { +// if case .emoji = self.type { +// return nil +// } +// +// let element = ActiveLabelAccessibilityElement(accessibilityContainer: accessibilityContainer) +// element.accessibilityTraits = .button +// element.accessibilityLabel = accessibilityLabelDescription +// element.accessibilityValue = accessibilityValueDescription +// return element +// } +//} -import UIKit -import Foundation -import ActiveLabel -import os.log -import MastodonUI - -extension ActiveLabel { - - enum Style { - case `default` - case statusHeader - case statusName - case profileFieldName - case profileFieldValue - } - - convenience init(style: Style) { - self.init() - - numberOfLines = 0 - lineSpacing = 5 - mentionColor = Asset.Colors.brandBlue.color - hashtagColor = Asset.Colors.brandBlue.color - URLColor = Asset.Colors.brandBlue.color - emojiPlaceholderColor = .systemFill - - accessibilityContainerType = .semanticGroup - - switch style { - case .default: - font = .preferredFont(forTextStyle: .body) - textColor = Asset.Colors.Label.primary.color - case .statusHeader: - font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium), maximumPointSize: 17) - textColor = Asset.Colors.Label.secondary.color - numberOfLines = 1 - case .statusName: - font = .systemFont(ofSize: 17, weight: .semibold) - textColor = Asset.Colors.Label.primary.color - numberOfLines = 1 - case .profileFieldName: - font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 20) - textColor = Asset.Colors.Label.primary.color - numberOfLines = 1 - case .profileFieldValue: - font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 20) - textColor = Asset.Colors.Label.primary.color - numberOfLines = 1 - } - } - -} - -extension ActiveLabel { - public func configure(text: String) { - attributedText = nil - activeEntities.removeAll() - self.text = text - accessibilityLabel = text - } -} - -extension ActiveLabel { - - /// status content - public func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) { - attributedText = nil - activeEntities.removeAll() - - if let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) { - text = parseResult.trimmed - activeEntities = parseResult.activeEntities - accessibilityLabel = parseResult.original - } else { - text = "" - accessibilityLabel = nil - } - } - - public func configure(contentParseResult parseResult: MastodonStatusContent.ParseResult?) { - attributedText = nil - activeEntities.removeAll() - text = parseResult?.trimmed ?? "" - activeEntities = parseResult?.activeEntities ?? [] - accessibilityLabel = parseResult?.original ?? nil - } - - /// account note - public func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) { - configure(content: note, emojiDict: emojiDict) - } -} - -extension ActiveLabel { - /// account field - public func configure(field: String, emojiDict: MastodonStatusContent.EmojiDict) { - configure(content: field, emojiDict: emojiDict) - } -} - -extension ActiveEntity { - - var accessibilityLabelDescription: String { - switch self.type { - case .email: return L10n.Common.Controls.Status.Tag.email - case .hashtag: return L10n.Common.Controls.Status.Tag.hashtag - case .mention: return L10n.Common.Controls.Status.Tag.mention - case .url: return L10n.Common.Controls.Status.Tag.url - case .emoji: return L10n.Common.Controls.Status.Tag.emoji - } - } - - var accessibilityValueDescription: String { - switch self.type { - case .email(let text, _): return text - case .hashtag(let text, _): return text - case .mention(let text, _): return text - case .url(_, let trimmed, _, _): return trimmed - case .emoji(let text, _, _): return text - } - } - - func accessibilityElement(in accessibilityContainer: Any) -> ActiveLabelAccessibilityElement? { - if case .emoji = self.type { - return nil - } - - let element = ActiveLabelAccessibilityElement(accessibilityContainer: accessibilityContainer) - element.accessibilityTraits = .button - element.accessibilityLabel = accessibilityLabelDescription - element.accessibilityValue = accessibilityValueDescription - return element - } -} - -final class ActiveLabelAccessibilityElement: UIAccessibilityElement { - var index: Int! -} - +//final class ActiveLabelAccessibilityElement: UIAccessibilityElement { +// var index: Int! +//} +// // MARK: - UIAccessibilityContainer -extension ActiveLabel { - - func createAccessibilityElements() -> [UIAccessibilityElement] { - var elements: [UIAccessibilityElement] = [] - - let element = ActiveLabelAccessibilityElement(accessibilityContainer: self) - element.accessibilityTraits = .staticText - element.accessibilityLabel = accessibilityLabel - element.accessibilityFrame = superview!.convert(frame, to: nil) - element.accessibilityLanguage = accessibilityLanguage - elements.append(element) - - for entity in activeEntities { - guard let element = entity.accessibilityElement(in: self) else { continue } - var glyphRange = NSRange() - layoutManager.characterRange(forGlyphRange: entity.range, actualGlyphRange: &glyphRange) - let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) - element.accessibilityFrame = self.convert(rect, to: nil) - element.accessibilityContainer = self - elements.append(element) - } - - return elements - } - -} +//extension ActiveLabel { +// +// func createAccessibilityElements() -> [UIAccessibilityElement] { +// var elements: [UIAccessibilityElement] = [] +// +// let element = ActiveLabelAccessibilityElement(accessibilityContainer: self) +// element.accessibilityTraits = .staticText +// element.accessibilityLabel = accessibilityLabel +// element.accessibilityFrame = superview!.convert(frame, to: nil) +// element.accessibilityLanguage = accessibilityLanguage +// elements.append(element) +// +// for entity in activeEntities { +// guard let element = entity.accessibilityElement(in: self) else { continue } +// var glyphRange = NSRange() +// layoutManager.characterRange(forGlyphRange: entity.range, actualGlyphRange: &glyphRange) +// let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) +// element.accessibilityFrame = self.convert(rect, to: nil) +// element.accessibilityContainer = self +// elements.append(element) +// } +// +// return elements +// } +// +//} diff --git a/Mastodon/Extension/CoreDataStack/Emojis.swift b/Mastodon/Extension/CoreDataStack/Emojis.swift index 8d7c29753..2f45880bd 100644 --- a/Mastodon/Extension/CoreDataStack/Emojis.swift +++ b/Mastodon/Extension/CoreDataStack/Emojis.swift @@ -23,15 +23,6 @@ extension EmojiContainer { let decoder = JSONDecoder() return emojisData.flatMap { try? decoder.decode([Mastodon.Entity.Emoji].self, from: $0) } } - - var emojiDict: MastodonStatusContent.EmojiDict { - var dict = MastodonStatusContent.EmojiDict() - for emoji in emojis ?? [] { - guard let url = URL(string: emoji.url) else { continue } - dict[emoji.shortcode] = url - } - return dict - } var emojiMeta: MastodonContent.Emojis { var dict = MastodonContent.Emojis() diff --git a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift index 8109371c4..de703fd16 100644 --- a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift +++ b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift @@ -7,6 +7,7 @@ import UIKit import MastodonSDK +import MastodonMeta extension Mastodon.Entity.Account: Hashable { public func hash(into hasher: inout Hasher) { @@ -28,3 +29,13 @@ extension Mastodon.Entity.Account { return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")! } } + +extension Mastodon.Entity.Account { + var emojiMeta: MastodonContent.Emojis { + var dict = MastodonContent.Emojis() + for emoji in emojis ?? [] { + dict[emoji.shortcode] = emoji.url + } + return dict + } +} diff --git a/Mastodon/Extension/MetaLabel.swift b/Mastodon/Extension/MetaLabel.swift index 67200874b..27f04e068 100644 --- a/Mastodon/Extension/MetaLabel.swift +++ b/Mastodon/Extension/MetaLabel.swift @@ -6,14 +6,19 @@ // import UIKit +import Meta import MetaTextKit extension MetaLabel { enum Style { case statusHeader case statusName -// case profileFieldName -// case profileFieldValue + case notificationName + case profileFieldName + case profileFieldValue + case recommendAccountName + case titleView + case settingTableFooter } convenience init(style: Style) { @@ -33,14 +38,37 @@ extension MetaLabel { case .statusName: font = .systemFont(ofSize: 17, weight: .semibold) textColor = Asset.Colors.Label.primary.color -// case .profileFieldName: -// font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 20) -// textColor = Asset.Colors.Label.primary.color -// numberOfLines = 1 -// case .profileFieldValue: -// font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 20) -// textColor = Asset.Colors.Label.primary.color -// numberOfLines = 1 + + case .notificationName: + font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20) + textColor = Asset.Colors.brandBlue.color + + case .profileFieldName: + font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 20) + textColor = Asset.Colors.Label.primary.color + + case .profileFieldValue: + font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 20) + textColor = Asset.Colors.Label.primary.color + textAlignment = .right + + + case .titleView: + font = .systemFont(ofSize: 17, weight: .semibold) + textColor = Asset.Colors.Label.primary.color + textAlignment = .center + paragraphStyle.alignment = .center + + case .recommendAccountName: + font = .systemFont(ofSize: 18, weight: .semibold) + textColor = .white + + case .settingTableFooter: + font = .preferredFont(forTextStyle: .body) + textColor = Asset.Colors.Label.primary.color + numberOfLines = 0 + textContainer.maximumNumberOfLines = 0 + paragraphStyle.alignment = .center } self.font = font @@ -50,4 +78,23 @@ extension MetaLabel { .font: font, .foregroundColor: textColor ] - }} + linkAttributes = [ + .font: font, + .foregroundColor: Asset.Colors.brandBlue.color + ] + } + +} + +struct PlaintextMetaContent: MetaContent { + let string: String + let entities: [Meta.Entity] = [] + + init(string: String) { + self.string = string + } + + func metaAttachment(for entity: Meta.Entity) -> MetaAttachment? { + return nil + } +} diff --git a/Mastodon/Helper/MastodonField.swift b/Mastodon/Helper/MastodonField.swift deleted file mode 100644 index 86bc2cc9b..000000000 --- a/Mastodon/Helper/MastodonField.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// MastodonField.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-3-30. -// - -//import Foundation -//import ActiveLabel -// -//enum MastodonField { -// -// @available(*, deprecated, message: "rely on server meta rendering") -// public static func parse(field string: String, emojiDict: MastodonStatusContent.EmojiDict) -> ParseResult { -// // use content parser get emoji entities -// let value = string -// -// var string = string -// var entities: [ActiveEntity] = [] -// -// do { -// let contentParseresult = try MastodonStatusContent.parse(content: string, emojiDict: emojiDict) -// string = contentParseresult.trimmed -// entities.append(contentsOf: contentParseresult.activeEntities) -// } catch { -// // assertionFailure(error.localizedDescription) -// } -// -// let mentionMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?)") -// let hashtagMatches = string.matches(pattern: "(?:#([^\\s.]+))") -// let urlMatches = string.matches(pattern: "(?i)https?://\\S+(?:/|\\b)") -// -// -// for match in mentionMatches { -// guard let text = string.substring(with: match, at: 0) else { continue } -// let entity = ActiveEntity(range: match.range, type: .mention(text, userInfo: nil)) -// entities.append(entity) -// } -// -// for match in hashtagMatches { -// guard let text = string.substring(with: match, at: 0) else { continue } -// let entity = ActiveEntity(range: match.range, type: .hashtag(text, userInfo: nil)) -// entities.append(entity) -// } -// -// for match in urlMatches { -// guard let text = string.substring(with: match, at: 0) else { continue } -// let entity = ActiveEntity(range: match.range, type: .url(text, trimmed: text, url: text, userInfo: nil)) -// entities.append(entity) -// } -// -// return ParseResult(value: value, trimmed: string, activeEntities: entities) -// } -// -//} -// -//extension MastodonField { -// public struct ParseResult { -// let value: String -// let trimmed: String -// let activeEntities: [ActiveEntity] -// } -//} diff --git a/Mastodon/Helper/MastodonStatusContent+Appearance.swift b/Mastodon/Helper/MastodonStatusContent+Appearance.swift deleted file mode 100644 index d82d68b85..000000000 --- a/Mastodon/Helper/MastodonStatusContent+Appearance.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// MastodonStatusContent+Appearance.swift -// Mastodon -// -// Created by Cirno MainasuK on 2021-6-20. -// - -import UIKit - -extension MastodonStatusContent { - public struct Appearance { - let attributes: [NSAttributedString.Key: Any] - let urlAttributes: [NSAttributedString.Key: Any] - let hashtagAttributes: [NSAttributedString.Key: Any] - let mentionAttributes: [NSAttributedString.Key: Any] - } -} diff --git a/Mastodon/Helper/MastodonStatusContent+ParseResult.swift b/Mastodon/Helper/MastodonStatusContent+ParseResult.swift deleted file mode 100644 index 9de0b341f..000000000 --- a/Mastodon/Helper/MastodonStatusContent+ParseResult.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// MastodonStatusContent+ParseResult.swift -// Mastodon -// -// Created by Cirno MainasuK on 2021-6-20. -// - -import Foundation -import ActiveLabel - -extension MastodonStatusContent { - public struct ParseResult: Hashable { - public let document: String - public let original: String - public let trimmed: String - public let activeEntities: [ActiveEntity] - - public static func == (lhs: MastodonStatusContent.ParseResult, rhs: MastodonStatusContent.ParseResult) -> Bool { - return lhs.document == rhs.document - && lhs.original == rhs.original - && lhs.trimmed == rhs.trimmed - && lhs.activeEntities.count == rhs.activeEntities.count // FIXME: - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(document) - hasher.combine(original) - hasher.combine(trimmed) - hasher.combine(activeEntities.count) // FIXME: - } - - func trimmedAttributedString(appearance: MastodonStatusContent.Appearance) -> NSAttributedString { - let attributedString = NSMutableAttributedString(string: trimmed, attributes: appearance.attributes) - for entity in activeEntities { - switch entity.type { - case .url: - attributedString.addAttributes(appearance.urlAttributes, range: entity.range) - case .hashtag: - attributedString.addAttributes(appearance.hashtagAttributes, range: entity.range) - case .mention: - attributedString.addAttributes(appearance.mentionAttributes, range: entity.range) - default: - break - } - if let uri = entity.type.uri { - attributedString.addAttributes([ - .link: uri - ], range: entity.range) - } - } - return attributedString - } - } -} - -extension ActiveEntityType { - - static let appScheme = "mastodon" - - public init?(url: URL) { - guard let scheme = url.scheme?.lowercased() else { return nil } - guard scheme == ActiveEntityType.appScheme else { - self = .url("", trimmed: "", url: url.absoluteString, userInfo: nil) - return - } - - guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), - let parameters = components.queryItems else { return nil } - - if let hashtag = parameters.first(where: { $0.name == "hashtag" }), let encoded = hashtag.value, let value = String(base64Encoded: encoded) { - self = .hashtag(value, userInfo: nil) - return - } - if let mention = parameters.first(where: { $0.name == "mention" }), let encoded = mention.value, let value = String(base64Encoded: encoded) { - self = .mention(value, userInfo: nil) - return - } - return nil - } - - public var uri: URL? { - switch self { - case .url(_, _, let url, _): - return URL(string: url) - case .hashtag(let hashtag, _): - return URL(string: "\(ActiveEntityType.appScheme)://meta?hashtag=\(hashtag.base64Encoded)") - case .mention(let mention, _): - return URL(string: "\(ActiveEntityType.appScheme)://meta?mention=\(mention.base64Encoded)") - default: - return nil - } - } - -} - -extension String { - fileprivate var base64Encoded: String { - return Data(self.utf8).base64EncodedString() - } - - init?(base64Encoded: String) { - guard let data = Data(base64Encoded: base64Encoded), - let string = String(data: data, encoding: .utf8) else { - return nil - } - self = string - } -} diff --git a/Mastodon/Helper/MastodonStatusContent.swift b/Mastodon/Helper/MastodonStatusContent.swift deleted file mode 100755 index 917c456d2..000000000 --- a/Mastodon/Helper/MastodonStatusContent.swift +++ /dev/null @@ -1,332 +0,0 @@ -// -// MastodonStatusContent.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021/2/1. -// - -import UIKit -import Combine -import ActiveLabel -import Fuzi - -public enum MastodonStatusContent { - - public typealias EmojiShortcode = String - public typealias EmojiDict = [EmojiShortcode: URL] - - static let workingQueue = DispatchQueue(label: "org.joinmastodon.app.ActiveLabel.working-queue", qos: .userInteractive, attributes: .concurrent) - - public static func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> AnyPublisher { - return Future { promise in - self.workingQueue.async { - let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) - promise(.success(parseResult)) - } - } - .eraseToAnyPublisher() - } - - public static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult { - let document: String = { - var content = content.replacingOccurrences(of: "

", with: "

\r\n") - for (shortcode, url) in emojiDict { - let emojiNode = "\(shortcode)" - let pattern = ":\(shortcode):" - content = content.replacingOccurrences(of: pattern, with: emojiNode) - } - return content.trimmingCharacters(in: .whitespacesAndNewlines) - }() - let rootNode = try Node.parse(document: document) - let text = String(rootNode.text) - - var activeEntities: [ActiveEntity] = [] - let entities = MastodonStatusContent.Node.entities(in: rootNode) - for entity in entities { - let range = NSRange(entity.text.startIndex.. String { - guard self.hasPrefix(prefix) else { return self } - return String(self.dropFirst(prefix.count)) - } -} - -extension MastodonStatusContent { - - class Node { - - let level: Int - let type: Type? - - // substring text - let text: Substring - - // range in parent String - var range: Range { - return text.startIndex.. = { - guard let className = attributes["class"] else { return Set() } - return Set(className.components(separatedBy: " ")) - }() - let _type: Type? = { - if tagName == "a" { - if _classNames.contains("u-url") { - return .mention - } - if _classNames.contains("hashtag") { - return .hashtag - } - return .url - } else { - if _classNames.contains("emoji") { - return .emoji - } - return nil - } - }() - self.level = level - self.type = _type - self.text = text - self.tagName = tagName - self.attributes = attributes - self.href = href - self.hrefEllipsis = hrefEllipsis - self.children = children - } - - static func parse(document: String) throws -> MastodonStatusContent.Node { - let document = document.replacingOccurrences(of: "
|
", with: "\r\n", options: .regularExpression, range: nil) - let html = try HTMLDocument(string: document) - - let body = html.body ?? nil - let text = body?.stringValue ?? "" - let level = 0 - let children: [MastodonStatusContent.Node] = body.flatMap { body in - return Node.parse(element: body, parentText: text[...], parentLevel: level + 1) - } ?? [] - let node = Node( - level: level, - text: text[...], - tagName: body?.tag, - attributes: body?.attributes ?? [:], - href: nil, - hrefEllipsis: nil, - children: children - ) - - return node - } - - static func parse(element: XMLElement, parentText: Substring, parentLevel: Int) -> [Node] { - let parent = element - let scanner = Scanner(string: String(parentText)) - scanner.charactersToBeSkipped = .none - - var children: [Node] = [] - for _element in parent.children { - let _text = _element.stringValue - - // scan element text - _ = scanner.scanUpToString(_text) - let startIndexOffset = scanner.currentIndex.utf16Offset(in: scanner.string) - guard scanner.scanString(_text) != nil else { - assertionFailure() - continue - } - let endIndexOffset = scanner.currentIndex.utf16Offset(in: scanner.string) - - // locate substring - let startIndex = parentText.utf16.index(parentText.utf16.startIndex, offsetBy: startIndexOffset) - let endIndex = parentText.utf16.index(parentText.utf16.startIndex, offsetBy: endIndexOffset) - let text = Substring(parentText.utf16[startIndex.. Bool - ) -> [Node] { - var nodes: [Node] = [] - - if predicate(node) { - nodes.append(node) - } - - for child in node.children { - nodes.append(contentsOf: Node.collect(node: child, where: predicate)) - } - return nodes - } - - } - -} - -extension MastodonStatusContent.Node { - enum `Type` { - case url - case mention - case hashtag - case emoji - } - - static func entities(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] { - return MastodonStatusContent.Node.collect(node: node) { node in node.type != nil } - } - - static func hashtags(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] { - return MastodonStatusContent.Node.collect(node: node) { node in node.type == .hashtag } - } - - static func mentions(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] { - return MastodonStatusContent.Node.collect(node: node) { node in node.type == .mention } - } - - static func urls(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] { - return MastodonStatusContent.Node.collect(node: node) { node in node.type == .url } - } - -} - -extension MastodonStatusContent.Node: CustomDebugStringConvertible { - var debugDescription: String { - let linkInfo: String = { - switch (href, hrefEllipsis) { - case (nil, nil): - return "" - case (let href, let hrefEllipsis): - return "(\(href ?? "nil") - \(hrefEllipsis ?? "nil"))" - } - }() - let classNamesInfo: String = { - guard let className = attributes["class"] else { return "" } - return "@[\(className)]" - }() - let nodeDescription = String( - format: "<%@>%@%@: %@", - tagName ?? "", - classNamesInfo, - linkInfo, - String(text) - ) - guard !children.isEmpty else { - return nodeDescription - } - - let indent = Array(repeating: " ", count: level).joined() - let childrenDescription = children - .map { indent + $0.debugDescription } - .joined(separator: "\n") - - return nodeDescription + "\n" + childrenDescription - } -} diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusNodeDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusNodeDelegate.swift index 918e960f4..3c6d7da19 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusNodeDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusNodeDelegate.swift @@ -8,13 +8,10 @@ #if ASDK import Foundation -import ActiveLabel // MARK: - StatusViewDelegate extension StatusNodeDelegate where Self: StatusProvider { - func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType) { - StatusProviderFacade.responseToStatusActiveLabelAction(provider: self, node: node, didSelectActiveEntityType: type) - } + } #endif diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift index 544de8893..5dab05295 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift @@ -11,7 +11,6 @@ import Combine import CoreData import CoreDataStack import MastodonSDK -import ActiveLabel import Meta import MetaTextKit @@ -25,10 +24,6 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, avatarImageViewDidPressed imageView: UIImageView) { StatusProviderFacade.coordinateToStatusAuthorProfileScene(for: .primary, provider: self, cell: cell) } - - func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - StatusProviderFacade.responseToStatusActiveLabelAction(provider: self, cell: cell, activeLabel: activeLabel, didTapEntity: entity) - } func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) { StatusProviderFacade.responseToStatusMetaTextAction(provider: self, cell: cell, metaText: metaText, didSelectMeta: meta) diff --git a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift index e5bb15a80..e115151ea 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift @@ -11,7 +11,6 @@ import Combine import CoreData import CoreDataStack import MastodonSDK -import ActiveLabel import Meta import MetaTextKit @@ -125,35 +124,6 @@ extension StatusProviderFacade { } extension StatusProviderFacade { - - static func responseToStatusActiveLabelAction(provider: StatusProvider, cell: UITableViewCell, activeLabel: ActiveLabel, didTapEntity entity: ActiveEntity) { - switch entity.type { - case .url(_, _, let url, _), - .mention(let url, _) where url.lowercased().hasPrefix("http"): - // note: - // some server mark the normal url as "u-url" class. : - guard let url = URL(string: url) else { return } - if let domain = provider.context.authenticationService.activeMastodonAuthenticationBox.value?.domain, url.host == domain, - url.pathComponents.count >= 4, - url.pathComponents[0] == "/", - url.pathComponents[1] == "web", - url.pathComponents[2] == "statuses" { - let statusID = url.pathComponents[3] - let threadViewModel = RemoteThreadViewModel(context: provider.context, statusID: statusID) - provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) - } else { - provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - } - case .hashtag(let text, _): - let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, hashtag: text) - provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show) - case .mention(let text, let userInfo): - let href = userInfo?["href"] as? String - coordinateToStatusMentionProfileScene(for: .primary, provider: provider, cell: cell, mention: text, href: href) - default: - break - } - } static func responseToStatusMetaTextAction(provider: StatusProvider, cell: UITableViewCell, metaText: MetaText, didSelectMeta meta: Meta) { switch meta { @@ -185,31 +155,6 @@ extension StatusProviderFacade { } #if ASDK - static func responseToStatusActiveLabelAction(provider: StatusProvider, node: ASCellNode, didSelectActiveEntityType type: ActiveEntityType) { - switch type { - case .hashtag(let text, _): - let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, hashtag: text) - provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show) - case .mention(let text, _): - coordinateToStatusMentionProfileScene(for: .primary, provider: provider, node: node, mention: text) - case .url(_, _, let url, _): - guard let url = URL(string: url) else { return } - if let domain = provider.context.authenticationService.activeMastodonAuthenticationBox.value?.domain, url.host == domain, - url.pathComponents.count >= 4, - url.pathComponents[0] == "/", - url.pathComponents[1] == "web", - url.pathComponents[2] == "statuses" { - let statusID = url.pathComponents[3] - let threadViewModel = RemoteThreadViewModel(context: provider.context, statusID: statusID) - provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) - } else { - provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - } - default: - break - } - } - private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, node: ASCellNode, mention: String) { guard let status = provider.status(node: node, indexPath: nil) else { return } coordinateToStatusMentionProfileScene(for: target, provider: provider, status: status, mention: mention, href: nil) diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift index d970b6734..ffeae7678 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift @@ -40,20 +40,19 @@ final class ComposeStatusContentTableViewCell: UITableViewCell { attributes: attributes ) }() - let paragraphStyle: NSMutableParagraphStyle = { + metaText.paragraphStyle = { let style = NSMutableParagraphStyle() style.lineSpacing = 5 + style.paragraphSpacing = 8 return style }() metaText.textAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)), .foregroundColor: Asset.Colors.Label.primary.color, - .paragraphStyle: paragraphStyle, ] metaText.linkAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)), .foregroundColor: Asset.Colors.brandBlue.color, - .paragraphStyle: paragraphStyle, ] return metaText }() diff --git a/Mastodon/Scene/Compose/CollectionViewCell/CustomEmojiPickerItemCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/CustomEmojiPickerItemCollectionViewCell.swift index d88b919f4..c92e689bc 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/CustomEmojiPickerItemCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/CustomEmojiPickerItemCollectionViewCell.swift @@ -6,14 +6,14 @@ // import UIKit -import FLAnimatedImage +import SDWebImage final class CustomEmojiPickerItemCollectionViewCell: UICollectionViewCell { static let itemSize = CGSize(width: 44, height: 44) - let emojiImageView: FLAnimatedImageView = { - let imageView = FLAnimatedImageView() + let emojiImageView: SDAnimatedImageView = { + let imageView = SDAnimatedImageView() imageView.contentMode = .scaleAspectFit imageView.layer.masksToBounds = true return imageView diff --git a/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift index 2642808d1..79fe538d3 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift @@ -193,8 +193,15 @@ extension ComposeViewModel: UITableViewDataSource { // set avatar cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL())) - // set name username - cell.statusView.nameLabel.configure(content: status.author.displayNameWithFallback, emojiDict: status.author.emojiDict) + // set name, username + do { + let mastodonContent = MastodonContent(content: status.author.displayNameWithFallback, emojis: status.author.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.statusView.nameLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: status.author.displayNameWithFallback) + cell.statusView.nameLabel.configure(content: metaContent) + } cell.statusView.usernameLabel.text = "@" + status.author.acct // set text let content = MastodonContent(content: status.content, emojis: status.emojiMeta) @@ -226,13 +233,14 @@ extension ComposeViewModel: UITableViewDataSource { let name = author.displayName.isEmpty ? author.username : author.displayName return L10n.Scene.Compose.replyingToUser(name) }() - MastodonStatusContent.parseResult(content: headerText, emojiDict: replyTo.author.emojiDict) - .receive(on: DispatchQueue.main) - .sink { [weak cell] parseResult in - guard let cell = cell else { return } - cell.statusView.headerInfoLabel.configure(contentParseResult: parseResult) - } - .store(in: &cell.disposeBag) + do { + let mastodonContent = MastodonContent(content: headerText, emojis: replyTo.author.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.statusView.headerInfoLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: headerText) + cell.statusView.headerInfoLabel.configure(content: metaContent) + } } // configure author ComposeStatusSection.configureStatusContent(cell: cell, attribute: composeStatusAttribute) diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 2e059c1a3..52af2cb67 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -181,7 +181,7 @@ final class ComposeViewModel: NSObject { } return displayName }() - self.composeStatusAttribute.emojiDict.value = mastodonUser?.emojiDict ?? [:] + self.composeStatusAttribute.emojiMeta.value = mastodonUser?.emojiMeta ?? [:] self.composeStatusAttribute.username.value = username } .store(in: &disposeBag) diff --git a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift index 8e6d46878..8948a2f6a 100644 --- a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift +++ b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift @@ -7,7 +7,6 @@ import os.log import UIKit -import ActiveLabel import FLAnimatedImage import MetaTextKit @@ -52,12 +51,7 @@ final class ReplicaStatusView: UIView { return label }() - let headerInfoLabel: ActiveLabel = { - let label = ActiveLabel(style: .statusHeader) - label.text = "Bob reblogged" - label.layer.masksToBounds = false - return label - }() + let headerInfoLabel = MetaLabel(style: .statusHeader) let avatarView: UIView = { let view = UIView() @@ -68,10 +62,7 @@ final class ReplicaStatusView: UIView { }() let avatarImageView = FLAnimatedImageView() - let nameLabel: ActiveLabel = { - let label = ActiveLabel(style: .statusName) - return label - }() + let nameLabel = MetaLabel(style: .statusName) let nameTrialingDotLabel: UILabel = { let label = UILabel() diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift index c0de5c532..8d4a8b37f 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift @@ -56,7 +56,7 @@ extension HashtagTimelineViewController { super.viewDidLoad() title = "#\(viewModel.hashtag)" - titleView.update(title: viewModel.hashtag, subtitle: nil, emojiDict: [:]) + titleView.update(title: viewModel.hashtag, subtitle: nil) navigationItem.titleView = titleView view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor @@ -150,7 +150,7 @@ extension HashtagTimelineViewController { private func updatePromptTitle() { var subtitle: String? defer { - titleView.update(title: "#" + viewModel.hashtag, subtitle: subtitle, emojiDict: [:]) + titleView.update(title: "#" + viewModel.hashtag, subtitle: subtitle) } guard let histories = viewModel.hashtagEntity.value?.history else { return @@ -209,7 +209,7 @@ extension HashtagTimelineViewController: LoadMoreConfigurableTableViewContainer typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell typealias LoadingState = HashtagTimelineViewModel.LoadOldestState.Loading var loadMoreConfigurableTableView: UITableView { return tableView } - var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadoldestStateMachine } + var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadOldestStateMachine } } // MARK: - UITableViewDelegate diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift index a90afdad0..093258733 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift @@ -84,7 +84,7 @@ extension HashtagTimelineViewModel { newSnapshot.appendItems(statusItemList, toSection: .main) } - if !(self.loadoldestStateMachine.currentState is LoadOldestState.NoMore) { + if !(self.loadOldestStateMachine.currentState is LoadOldestState.NoMore) { newSnapshot.appendItems([.bottomLoader], toSection: .main) } diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift index 54acbf4fe..c1698f5dc 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift @@ -46,7 +46,7 @@ final class HashtagTimelineViewModel: NSObject { }() lazy var loadLatestStateMachinePublisher = CurrentValueSubject(nil) // bottom loader - private(set) lazy var loadoldestStateMachine: GKStateMachine = { + private(set) lazy var loadOldestStateMachine: GKStateMachine = { // exclude timeline middle fetcher state let stateMachine = GKStateMachine(states: [ LoadOldestState.Initial(viewModel: self), diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+Provider.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+Provider.swift index 4896c58b9..5f97ebead 100644 --- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+Provider.swift +++ b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController+Provider.swift @@ -109,6 +109,12 @@ extension AsyncHomeTimelineViewController: StatusProvider { return nil } } + + func statusObjectItems(indexPaths: [IndexPath]) -> [StatusObjectItem] { + guard let diffableDataSource = self.viewModel.diffableDataSource else { return [] } + let items = indexPaths.compactMap { diffableDataSource.itemIdentifier(for: $0)?.statusObjectItem } + return items + } } diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift index c8ccfdd3d..c90b703e5 100644 --- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewController.swift @@ -86,7 +86,7 @@ extension AsyncHomeTimelineViewController { node.allowsSelection = true title = L10n.Scene.HomeTimeline.title - view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor navigationItem.leftBarButtonItem = settingBarButtonItem navigationItem.titleView = titleView titleView.delegate = self @@ -341,7 +341,7 @@ extension AsyncHomeTimelineViewController { // typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell // typealias LoadingState = HomeTimelineViewModel.LoadOldestState.Loading // var loadMoreConfigurableTableView: UITableView { return tableView } -// var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadoldestStateMachine } +// var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadOldestStateMachine } //} // MARK: - UITableViewDelegate @@ -556,7 +556,7 @@ extension AsyncHomeTimelineViewController: ASTableDelegate { } func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) { - viewModel.loadoldestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self) + viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self) context.completeBatchFetching(true) } diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+Diffable.swift index fbd5c1438..7799c2163 100644 --- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel+Diffable.swift @@ -106,7 +106,7 @@ extension AsyncHomeTimelineViewModel: NSFetchedResultsControllerDelegate { let endSnapshot = CACurrentMediaTime() - if shouldAddBottomLoader, !(self.loadoldestStateMachine.currentState is LoadOldestState.NoMore) { + if shouldAddBottomLoader, !(self.loadLatestStateMachine.currentState is LoadOldestState.NoMore) { newSnapshot.appendItems([.bottomLoader], toSection: .main) } diff --git a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel.swift index c33b91c02..d7ed0b10d 100644 --- a/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/AsyncHomeTimeline/AsyncHomeTimelineViewModel.swift @@ -18,7 +18,6 @@ import CoreDataStack import GameplayKit import AlamofireImage import DateToolsSwift -import ActiveLabel import AsyncDisplayKit final class AsyncHomeTimelineViewModel: NSObject { @@ -59,7 +58,7 @@ final class AsyncHomeTimelineViewModel: NSObject { }() lazy var loadLatestStateMachinePublisher = CurrentValueSubject(nil) // bottom loader - private(set) lazy var loadoldestStateMachine: GKStateMachine = { + private(set) lazy var loadOldestStateMachine: GKStateMachine = { // exclude timeline middle fetcher state let stateMachine = GKStateMachine(states: [ LoadOldestState.Initial(viewModel: self), diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 73650b6ea..e69d3bae2 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -388,7 +388,7 @@ extension HomeTimelineViewController: LoadMoreConfigurableTableViewContainer { typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell typealias LoadingState = HomeTimelineViewModel.LoadOldestState.Loading var loadMoreConfigurableTableView: UITableView { return tableView } - var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadoldestStateMachine } + var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadLatestStateMachine } } // MARK: - UITableViewDelegate diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift index b2ea2035c..d25f30aee 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift @@ -115,7 +115,7 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate { let endSnapshot = CACurrentMediaTime() DispatchQueue.main.async { - if shouldAddBottomLoader, !(self.loadoldestStateMachine.currentState is LoadOldestState.NoMore) { + if shouldAddBottomLoader, !(self.loadLatestStateMachine.currentState is LoadOldestState.NoMore) { newSnapshot.appendItems([.bottomLoader], toSection: .main) } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index 9d8f6f709..611e95368 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -15,7 +15,6 @@ import CoreDataStack import GameplayKit import AlamofireImage import DateToolsSwift -import ActiveLabel final class HomeTimelineViewModel: NSObject { @@ -52,7 +51,7 @@ final class HomeTimelineViewModel: NSObject { }() lazy var loadLatestStateMachinePublisher = CurrentValueSubject(nil) // bottom loader - private(set) lazy var loadoldestStateMachine: GKStateMachine = { + private(set) lazy var loadOldestStateMachine: GKStateMachine = { // exclude timeline middle fetcher state let stateMachine = GKStateMachine(states: [ LoadOldestState.Initial(viewModel: self), diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 5528adf68..100cd3d82 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -12,7 +12,6 @@ import GameplayKit import MastodonSDK import OSLog import UIKit -import ActiveLabel import Meta import MetaTextKit @@ -272,7 +271,7 @@ extension NotificationViewController { switch item { case .bottomLoader: if !tableView.isDragging, !tableView.isDecelerating { - viewModel.loadoldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self) + viewModel.loadOldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self) } default: break @@ -305,7 +304,7 @@ extension NotificationViewController: NotificationTableViewCellDelegate { } } - func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, authorNameLabelDidPressed label: ActiveLabel) { + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, authorNameLabelDidPressed label: MetaLabel) { guard let diffableDataSource = viewModel.diffableDataSource else { return } guard let indexPath = tableView.indexPath(for: cell) else { return } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } @@ -319,7 +318,6 @@ extension NotificationViewController: NotificationTableViewCellDelegate { } } - func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) { viewModel.acceptFollowRequest(notification: notification) } @@ -380,7 +378,7 @@ extension NotificationViewController: LoadMoreConfigurableTableViewContainer { typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell typealias LoadingState = NotificationViewModel.LoadOldestState.Loading var loadMoreConfigurableTableView: UITableView { tableView } - var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadoldestStateMachine } + var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadOldestStateMachine } } extension NotificationViewController { diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift index 8102f7705..712380917 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel.swift @@ -54,7 +54,7 @@ final class NotificationViewModel: NSObject { lazy var loadLatestStateMachinePublisher = CurrentValueSubject(nil) // bottom loader - private(set) lazy var loadoldestStateMachine: GKStateMachine = { + private(set) lazy var loadOldestStateMachine: GKStateMachine = { // exclude timeline middle fetcher state let stateMachine = GKStateMachine(states: [ LoadOldestState.Initial(viewModel: self), diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index 6afc61e9a..65e4dbcdb 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -10,7 +10,6 @@ import Combine import Foundation import CoreDataStack import UIKit -import ActiveLabel import MetaTextKit import Meta import FLAnimatedImage @@ -20,7 +19,7 @@ protocol NotificationTableViewCellDelegate: AnyObject { func parent() -> UIViewController func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, avatarImageViewDidPressed imageView: UIImageView) - func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, authorNameLabelDidPressed label: ActiveLabel) + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, authorNameLabelDidPressed label: MetaLabel) func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) @@ -58,13 +57,7 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { return label }() - let nameLabel: ActiveLabel = { - let label = ActiveLabel(style: .statusName) - label.textColor = Asset.Colors.brandBlue.color - label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20) - label.lineBreakMode = .byTruncatingTail - return label - }() + let nameLabel = MetaLabel(style: .notificationName) let buttonStackView = UIStackView() @@ -318,10 +311,6 @@ extension NotificationStatusTableViewCell: StatusViewDelegate { func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) { // do nothing } - - func statusView(_ statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - // do nothing - } func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) { delegate?.notificationStatusTableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta) diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift index 3af4f884a..c9890c248 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift @@ -57,7 +57,7 @@ extension FavoriteViewController { .store(in: &disposeBag) navigationItem.titleView = titleView - titleView.update(title: L10n.Scene.Favorite.title, subtitle: nil, emojiDict: [:]) + titleView.update(title: L10n.Scene.Favorite.title, subtitle: nil) tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index a88d3e06c..f0bf72af1 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -9,16 +9,16 @@ import os.log import UIKit import Combine import PhotosUI -import ActiveLabel import AlamofireImage import CropViewController import TwitterTextEditor import MastodonMeta +import MetaTextKit protocol ProfileHeaderViewControllerDelegate: AnyObject { func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView) func profileHeaderViewController(_ viewController: ProfileHeaderViewController, pageSegmentedControlValueChanged segmentedControl: UISegmentedControl, selectedSegmentIndex index: Int) - func profileHeaderViewController(_ viewController: ProfileHeaderViewController, profileFieldCollectionViewCell: ProfileFieldCollectionViewCell, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) + func profileHeaderViewController(_ viewController: ProfileHeaderViewController, profileFieldCollectionViewCell: ProfileFieldCollectionViewCell, metaLabel: MetaLabel, didSelectMeta meta: Meta) } final class ProfileHeaderViewController: UIViewController { @@ -35,6 +35,7 @@ final class ProfileHeaderViewController: UIViewController { let titleView: DoubleTitleLabelNavigationBarTitleView = { let titleView = DoubleTitleLabelNavigationBarTitleView() titleView.titleLabel.textColor = .white + titleView.titleLabel.textAttributes[.foregroundColor] = UIColor.white titleView.titleLabel.alpha = 0 titleView.subtitleLabel.textColor = .white titleView.subtitleLabel.alpha = 0 @@ -179,19 +180,14 @@ extension ProfileHeaderViewController { viewModel.isEditing, viewModel.displayProfileInfo.name.removeDuplicates(), viewModel.editProfileInfo.name.removeDuplicates(), - viewModel.emojiDict + viewModel.emojiMeta ) .receive(on: DispatchQueue.main) - .sink { [weak self] isEditing, name, editingName, emojiDict in + .sink { [weak self] isEditing, name, editingName, emojiMeta in guard let self = self else { return } do { - var emojis = MastodonContent.Emojis() - for (key, value) in emojiDict { - emojis[key] = value.absoluteString - } - let metaContent = try MastodonMetaContent.convert( - document: MastodonContent(content: name ?? " ", emojis: emojis) - ) + let mastodonContent = MastodonContent(content: name ?? " ", emojis: emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) self.profileHeaderView.nameMetaText.configure(content: metaContent) } catch { assertionFailure() @@ -200,25 +196,37 @@ extension ProfileHeaderViewController { } .store(in: &disposeBag) - Publishers.CombineLatest3( - viewModel.isEditing.eraseToAnyPublisher(), - viewModel.displayProfileInfo.note.removeDuplicates().eraseToAnyPublisher(), - viewModel.editProfileInfo.note.removeDuplicates().eraseToAnyPublisher() + Publishers.CombineLatest4( + viewModel.isEditing.removeDuplicates(), + viewModel.displayProfileInfo.note.removeDuplicates(), + viewModel.editProfileInfo.note.removeDuplicates(), + viewModel.emojiMeta.removeDuplicates() ) .receive(on: DispatchQueue.main) - .sink { [weak self] isEditing, note, editingNote in + .sink { [weak self] isEditing, note, editingNote, emojiMeta in guard let self = self else { return } - self.profileHeaderView.bioActiveLabel.configure(note: note ?? "", emojiDict: [:]) // FIXME: custom emoji - // prevent duplicate set - let editingNote = editingNote ?? "" - if self.profileHeaderView.bioTextEditorView.text != editingNote { - self.profileHeaderView.bioTextEditorView.text = editingNote + self.profileHeaderView.bioMetaText.textView.isEditable = isEditing + + if isEditing { + if self.profileHeaderView.bioMetaText.backedString != note { + let metaContent = PlaintextMetaContent(string: editingNote ?? "") + self.profileHeaderView.bioMetaText.configure(content: metaContent) + } + } else { + let mastodonContent = MastodonContent(content: note ?? "", emojis: emojiMeta) + do { + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + self.profileHeaderView.bioMetaText.configure(content: metaContent) + } catch { + assertionFailure() + self.profileHeaderView.bioMetaText.reset() + } } } .store(in: &disposeBag) - - profileHeaderView.bioTextEditorView.changeObserver = self + profileHeaderView.bioMetaText.delegate = self + NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: profileHeaderView.nameTextField) .receive(on: DispatchQueue.main) .sink { [weak self] notification in @@ -450,13 +458,16 @@ extension ProfileHeaderViewController { } -// MARK: - TextEditorViewChangeObserver -extension ProfileHeaderViewController: TextEditorViewChangeObserver { - func textEditorView(_ textEditorView: TextEditorView, didChangeWithChangeResult changeResult: TextEditorViewChangeResult) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: text: %s", ((#file as NSString).lastPathComponent), #line, #function, textEditorView.text) - guard changeResult.isTextChanged else { return } - assert(textEditorView === profileHeaderView.bioTextEditorView) - viewModel.editProfileInfo.note.value = textEditorView.text +// MARK: - MetaTextDelegate +extension ProfileHeaderViewController: MetaTextDelegate { + func metaText(_ metaText: MetaText, processEditing textStorage: MetaTextStorage) -> MetaContent? { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: text: %s", ((#file as NSString).lastPathComponent), #line, #function, metaText.backedString) + assert(metaText.textView === profileHeaderView.bioMetaText.textView) + if metaText.textView === profileHeaderView.bioMetaText.textView { + viewModel.editProfileInfo.note.value = metaText.backedString + } + + return nil } } @@ -533,6 +544,7 @@ extension ProfileHeaderViewController: UICollectionViewDelegate { // MARK: - ProfileFieldCollectionViewCellDelegate extension ProfileHeaderViewController: ProfileFieldCollectionViewCellDelegate { + // should be remove style edit button func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, editButtonDidPressed button: UIButton) { guard let diffableDataSource = viewModel.fieldDiffableDataSource else { return } @@ -541,8 +553,8 @@ extension ProfileHeaderViewController: ProfileFieldCollectionViewCellDelegate { viewModel.removeFieldItem(item: item) } - func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - delegate?.profileHeaderViewController(self, profileFieldCollectionViewCell: cell, activeLabel: activeLabel, didSelectActiveEntity: entity) + func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, metaLebel: MetaLabel, didSelectMeta meta: Meta) { + delegate?.profileHeaderViewController(self, profileFieldCollectionViewCell: cell, metaLabel: metaLebel, didSelectMeta: meta) } } diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift index 9a0145751..c98758581 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift @@ -10,6 +10,7 @@ import UIKit import Combine import Kanna import MastodonSDK +import MastodonMeta final class ProfileHeaderViewModel { @@ -24,7 +25,7 @@ final class ProfileHeaderViewModel { let needsSetupBottomShadow = CurrentValueSubject(true) let needsFiledCollectionViewHidden = CurrentValueSubject(false) let isTitleViewContentOffsetSet = CurrentValueSubject(false) - let emojiDict = CurrentValueSubject([:]) + let emojiMeta = CurrentValueSubject([:]) let accountForEdit = CurrentValueSubject(nil) // output @@ -58,10 +59,10 @@ final class ProfileHeaderViewModel { isEditing.removeDuplicates(), displayProfileInfo.fields.removeDuplicates(), editProfileInfo.fields.removeDuplicates(), - emojiDict.removeDuplicates() + emojiMeta.removeDuplicates() ) .receive(on: RunLoop.main) - .sink { [weak self] isEditing, displayFields, editingFields, emojiDict in + .sink { [weak self] isEditing, displayFields, editingFields, emojiMeta in guard let self = self else { return } guard let diffableDataSource = self.fieldDiffableDataSource else { return } @@ -87,7 +88,7 @@ final class ProfileHeaderViewModel { let attribute = oldFieldAttributeDict[field.id] ?? ProfileFieldItem.FieldItemAttribute() attribute.isEditing = isEditing - attribute.emojiDict.value = emojiDict + attribute.emojiMeta.value = emojiMeta attribute.isLast = false return ProfileFieldItem.field(field: field, attribute: attribute) } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileFieldAddEntryCollectionViewCell.swift b/Mastodon/Scene/Profile/Header/View/ProfileFieldAddEntryCollectionViewCell.swift index d8ecb198d..cafe0eda9 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileFieldAddEntryCollectionViewCell.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileFieldAddEntryCollectionViewCell.swift @@ -95,12 +95,12 @@ extension ProfileFieldAddEntryCollectionViewCell { bottomSeparatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self)).priority(.defaultHigh), ]) - fieldView.titleActiveLabel.isHidden = false - fieldView.titleActiveLabel.configure(field: L10n.Scene.Profile.Fields.addRow, emojiDict: [:]) + fieldView.titleMetaLabel.isHidden = false + fieldView.titleMetaLabel.configure(content: PlaintextMetaContent(string: L10n.Scene.Profile.Fields.addRow)) fieldView.titleTextField.isHidden = true - fieldView.valueActiveLabel.isHidden = false - fieldView.valueActiveLabel.configure(field: " ", emojiDict: [:]) + fieldView.valueMetaLabel.isHidden = false + fieldView.valueMetaLabel.configure(content: PlaintextMetaContent(string: " ")) fieldView.valueTextField.isHidden = true addGestureRecognizer(singleTagGestureRecognizer) diff --git a/Mastodon/Scene/Profile/Header/View/ProfileFieldCollectionViewCell.swift b/Mastodon/Scene/Profile/Header/View/ProfileFieldCollectionViewCell.swift index 6efa92e18..9106b0e44 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileFieldCollectionViewCell.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileFieldCollectionViewCell.swift @@ -8,11 +8,11 @@ import os.log import UIKit import Combine -import ActiveLabel +import MetaTextKit protocol ProfileFieldCollectionViewCellDelegate: AnyObject { func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, editButtonDidPressed button: UIButton) - func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) + func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, metaLebel: MetaLabel, didSelectMeta meta: Meta) } final class ProfileFieldCollectionViewCell: UICollectionViewCell { @@ -107,7 +107,7 @@ extension ProfileFieldCollectionViewCell { editButton.addTarget(self, action: #selector(ProfileFieldCollectionViewCell.editButtonDidPressed(_:)), for: .touchUpInside) - fieldView.valueActiveLabel.delegate = self + fieldView.valueMetaLabel.linkDelegate = self resetSeparatorLineLayout() } @@ -153,13 +153,11 @@ extension ProfileFieldCollectionViewCell { } } - - -// MARK: - ActiveLabelDelegate -extension ProfileFieldCollectionViewCell: ActiveLabelDelegate { - func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { +// MARK: - MetaLabelDelegate +extension ProfileFieldCollectionViewCell: MetaLabelDelegate { + func metaLabel(_ metaLabel: MetaLabel, didSelectMeta meta: Meta) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - delegate?.profileFieldCollectionViewCell(self, activeLabel: activeLabel, didSelectActiveEntity: entity) + delegate?.profileFieldCollectionViewCell(self, metaLebel: metaLabel, didSelectMeta: meta) } } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift b/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift index 956b8d6d9..ee17d7e4d 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift @@ -7,7 +7,7 @@ import UIKit import Combine -import ActiveLabel +import MetaTextKit final class ProfileFieldView: UIView { @@ -18,11 +18,7 @@ final class ProfileFieldView: UIView { let value = PassthroughSubject() // for custom emoji display - let titleActiveLabel: ActiveLabel = { - let label = ActiveLabel(style: .profileFieldName) - label.configure(content: "title", emojiDict: [:]) - return label - }() + let titleMetaLabel = MetaLabel(style: .profileFieldName) // for editing let titleTextField: UITextField = { @@ -34,12 +30,7 @@ final class ProfileFieldView: UIView { }() // for custom emoji display - let valueActiveLabel: ActiveLabel = { - let label = ActiveLabel(style: .profileFieldValue) - label.configure(content: "value", emojiDict: [:]) - label.textAlignment = .right - return label - }() + let valueMetaLabel = MetaLabel(style: .profileFieldValue) // for editing let valueTextField: UITextField = { @@ -81,10 +72,10 @@ extension ProfileFieldView { containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor), containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) - titleActiveLabel.translatesAutoresizingMaskIntoConstraints = false - containerStackView.addArrangedSubview(titleActiveLabel) + titleMetaLabel.translatesAutoresizingMaskIntoConstraints = false + containerStackView.addArrangedSubview(titleMetaLabel) NSLayoutConstraint.activate([ - titleActiveLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), + titleMetaLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), ]) titleTextField.setContentHuggingPriority(.defaultLow - 1, for: .horizontal) titleTextField.translatesAutoresizingMaskIntoConstraints = false @@ -94,12 +85,12 @@ extension ProfileFieldView { ]) titleTextField.setContentHuggingPriority(.defaultLow - 1, for: .horizontal) - valueActiveLabel.translatesAutoresizingMaskIntoConstraints = false - containerStackView.addArrangedSubview(valueActiveLabel) + valueMetaLabel.translatesAutoresizingMaskIntoConstraints = false + containerStackView.addArrangedSubview(valueMetaLabel) NSLayoutConstraint.activate([ - valueActiveLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), + valueMetaLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), ]) - valueActiveLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + valueMetaLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) valueTextField.translatesAutoresizingMaskIntoConstraints = false containerStackView.addArrangedSubview(valueTextField) NSLayoutConstraint.activate([ @@ -137,7 +128,8 @@ struct ProfileFieldView_Previews: PreviewProvider { static var previews: some View { UIViewPreview(width: 375) { let filedView = ProfileFieldView() - filedView.valueActiveLabel.configure(field: "https://mastodon.online", emojiDict: [:]) + let content = PlaintextMetaContent(string: "https://mastodon.online") + filedView.valueMetaLabel.configure(content: content) return filedView } .previewLayout(.fixed(width: 375, height: 100)) diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index 043e1b665..eb6e06a04 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -8,8 +8,6 @@ import os.log import UIKit import Combine -import ActiveLabel -import TwitterTextEditor import FLAnimatedImage import MetaTextKit @@ -17,7 +15,7 @@ protocol ProfileHeaderViewDelegate: AnyObject { func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView) func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, activeLabel: ActiveLabel, entityDidPressed entity: ActiveEntity) + func profileHeaderView(_ profileHeaderView: ProfileHeaderView, metaTextView: MetaTextView, metaDidPressed meta: Meta) func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed followingDashboardMeterView: ProfileStatusDashboardMeterView) @@ -166,28 +164,38 @@ final class ProfileHeaderView: UIView { }() let bioContainerView = UIView() - let bioContainerStackView = UIStackView() let fieldContainerStackView = UIStackView() - - let bioActiveLabelContainer: UIView = { - // use to set margin for active label - // the display/edit mode bio transition animation should without flicker with that - let view = UIView() - // note: comment out to see how it works - view.layoutMargins = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5) // magic from TextEditorView - return view - }() - let bioActiveLabel = ActiveLabel(style: .default) - let bioTextEditorView: TextEditorView = { - let textEditorView = TextEditorView() - textEditorView.scrollView.isScrollEnabled = false - textEditorView.isScrollEnabled = false - textEditorView.font = .preferredFont(forTextStyle: .body) - textEditorView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color - textEditorView.layer.masksToBounds = true - textEditorView.layer.cornerCurve = .continuous - textEditorView.layer.cornerRadius = 10 - return textEditorView + + let bioMetaText: MetaText = { + let metaText = MetaText() + metaText.textView.backgroundColor = .clear + metaText.textView.isEditable = false + metaText.textView.isSelectable = true + metaText.textView.isScrollEnabled = false + //metaText.textView.textContainer.lineFragmentPadding = 0 + //metaText.textView.textContainerInset = .zero + metaText.textView.layer.masksToBounds = false + metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment + + metaText.textView.layer.masksToBounds = true + metaText.textView.layer.cornerCurve = .continuous + metaText.textView.layer.cornerRadius = 10 + + metaText.paragraphStyle = { + let style = NSMutableParagraphStyle() + style.lineSpacing = 5 + style.paragraphSpacing = 8 + return style + }() + metaText.textAttributes = [ + .font: UIFont.preferredFont(forTextStyle: .body), + .foregroundColor: Asset.Colors.Label.primary.color, + ] + metaText.linkAttributes = [ + .font: UIFont.preferredFont(forTextStyle: .body), + .foregroundColor: Asset.Colors.brandBlue.color, + ] + return metaText }() static func createFieldCollectionViewLayout() -> UICollectionViewLayout { @@ -405,28 +413,15 @@ extension ProfileHeaderView { bioContainerView.preservesSuperviewLayoutMargins = true metaContainerStackView.addArrangedSubview(bioContainerView) - bioContainerStackView.translatesAutoresizingMaskIntoConstraints = false - bioContainerView.addSubview(bioContainerStackView) + bioMetaText.textView.translatesAutoresizingMaskIntoConstraints = false + bioContainerView.addSubview(bioMetaText.textView) NSLayoutConstraint.activate([ - bioContainerStackView.topAnchor.constraint(equalTo: bioContainerView.topAnchor), - bioContainerStackView.leadingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.leadingAnchor), - bioContainerStackView.trailingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.trailingAnchor), - bioContainerStackView.bottomAnchor.constraint(equalTo: bioContainerView.bottomAnchor), + bioMetaText.textView.topAnchor.constraint(equalTo: bioContainerView.topAnchor), + bioMetaText.textView.leadingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.leadingAnchor), + bioMetaText.textView.trailingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.trailingAnchor), + bioMetaText.textView.bottomAnchor.constraint(equalTo: bioContainerView.bottomAnchor), ]) - bioActiveLabel.translatesAutoresizingMaskIntoConstraints = false - bioActiveLabelContainer.addSubview(bioActiveLabel) - NSLayoutConstraint.activate([ - bioActiveLabel.topAnchor.constraint(equalTo: bioActiveLabelContainer.layoutMarginsGuide.topAnchor), - bioActiveLabel.leadingAnchor.constraint(equalTo: bioActiveLabelContainer.layoutMarginsGuide.leadingAnchor), - bioActiveLabel.trailingAnchor.constraint(equalTo: bioActiveLabelContainer.layoutMarginsGuide.trailingAnchor), - bioActiveLabel.bottomAnchor.constraint(equalTo: bioActiveLabelContainer.layoutMarginsGuide.bottomAnchor), - ]) - - bioContainerStackView.axis = .vertical - bioContainerStackView.addArrangedSubview(bioActiveLabelContainer) - bioContainerStackView.addArrangedSubview(bioTextEditorView) - fieldCollectionView.translatesAutoresizingMaskIntoConstraints = false metaContainerStackView.addArrangedSubview(fieldCollectionView) fieldCollectionViewHeightLayoutConstraint = fieldCollectionView.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh) @@ -445,7 +440,7 @@ extension ProfileHeaderView { bringSubviewToFront(bannerContainerView) bringSubviewToFront(nameContainerStackView) - bioActiveLabel.delegate = self + bioMetaText.textView.linkDelegate = self let avatarImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer avatarImageView.addGestureRecognizer(avatarImageViewSingleTapGestureRecognizer) @@ -479,9 +474,8 @@ extension ProfileHeaderView { nameMetaText.textView.alpha = 1 nameTextField.alpha = 0 nameTextField.isEnabled = false - bioActiveLabelContainer.isHidden = false - bioTextEditorView.isHidden = true - + bioMetaText.textView.backgroundColor = .clear + animator.addAnimations { self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundNormalColor self.nameTextFieldBackgroundView.backgroundColor = .clear @@ -494,17 +488,15 @@ extension ProfileHeaderView { nameMetaText.textView.alpha = 0 nameTextField.isEnabled = true nameTextField.alpha = 1 - bioActiveLabelContainer.isHidden = true - bioTextEditorView.isHidden = false editAvatarBackgroundView.isHidden = false editAvatarBackgroundView.alpha = 0 - bioTextEditorView.backgroundColor = .clear + bioMetaText.textView.backgroundColor = .clear animator.addAnimations { self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundEditingColor self.nameTextFieldBackgroundView.backgroundColor = Asset.Scene.Profile.Banner.nameEditBackgroundGray.color self.editAvatarBackgroundView.alpha = 1 - self.bioTextEditorView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color + self.bioMetaText.textView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color } } @@ -530,11 +522,11 @@ extension ProfileHeaderView { } } -// MARK: - ActiveLabelDelegate -extension ProfileHeaderView: ActiveLabelDelegate { - func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select entity: %s", ((#file as NSString).lastPathComponent), #line, #function, entity.primaryText) - delegate?.profileHeaderView(self, activeLabel: activeLabel, entityDidPressed: entity) +// MARK: - MetaTextViewDelegate +extension ProfileHeaderView: MetaTextViewDelegate { + func metaTextView(_ metaTextView: MetaTextView, didSelectMeta meta: Meta) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select entity", ((#file as NSString).lastPathComponent), #line, #function) + delegate?.profileHeaderView(self, metaTextView: metaTextView, metaDidPressed: meta) } } diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 1ac0c4a4e..fa54abf7b 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -8,7 +8,8 @@ import os.log import UIKit import Combine -import ActiveLabel +import MastodonMeta +import MetaTextKit final class ProfileViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -316,20 +317,26 @@ extension ProfileViewController { // bind view model Publishers.CombineLatest3( viewModel.name, - viewModel.emojiDict, + viewModel.emojiMeta, viewModel.statusesCount ) .receive(on: DispatchQueue.main) - .sink { [weak self] name, emojiDict, statusesCount in + .sink { [weak self] name, emojiMeta, statusesCount in guard let self = self else { return } guard let title = name, let statusesCount = statusesCount, let formattedStatusCount = MastodonMetricFormatter().string(from: statusesCount) else { self.titleView.isHidden = true return } - let subtitle = L10n.Plural.Count.MetricFormatted.post(formattedStatusCount, statusesCount) - self.titleView.update(title: title, subtitle: subtitle, emojiDict: emojiDict) self.titleView.isHidden = false + let subtitle = L10n.Plural.Count.MetricFormatted.post(formattedStatusCount, statusesCount) + let mastodonContent = MastodonContent(content: title, emojis: emojiMeta) + do { + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + self.titleView.update(titleMetaContent: metaContent, subtitle: subtitle) + } catch { + + } } .store(in: &disposeBag) viewModel.name @@ -391,9 +398,9 @@ extension ProfileViewController { viewModel.accountForEdit .assign(to: \.value, on: profileHeaderViewController.viewModel.accountForEdit) .store(in: &disposeBag) - viewModel.emojiDict + viewModel.emojiMeta .receive(on: DispatchQueue.main) - .assign(to: \.value, on: profileHeaderViewController.viewModel.emojiDict) + .assign(to: \.value, on: profileHeaderViewController.viewModel.emojiMeta) .store(in: &disposeBag) viewModel.username .map { username in username.flatMap { "@" + $0 } ?? " " } @@ -695,7 +702,7 @@ extension ProfileViewController: UIScrollViewDelegate { // MARK: - ProfileHeaderViewControllerDelegate extension ProfileViewController: ProfileHeaderViewControllerDelegate { - + func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView) { guard let scrollView = (profileSegmentedViewController.pagingViewController.currentViewController as? UserTimelineViewController)?.scrollView else { // assertionFailure() @@ -712,23 +719,24 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { ) } - func profileHeaderViewController(_ viewController: ProfileHeaderViewController, profileFieldCollectionViewCell: ProfileFieldCollectionViewCell, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - // handle profile fields interaction - switch entity.type { + func profileHeaderViewController(_ viewController: ProfileHeaderViewController, profileFieldCollectionViewCell: ProfileFieldCollectionViewCell, metaLabel: MetaLabel, didSelectMeta meta: Meta) { + switch meta { case .url(_, _, let url, _): guard let url = URL(string: url) else { return } coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - case .hashtag(let hashtag, _): + case .hashtag(_, let hashtag, _): let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag) coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show) - case .mention(_, let userInfo): + case .mention(_, _, let userInfo): guard let href = userInfo?["href"] as? String else { // currently we cannot present profile scene without userID return } guard let url = URL(string: href) else { return } coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - default: + case .email: + break + case .emoji: break } } @@ -758,7 +766,7 @@ extension ProfileViewController: ProfilePagingViewControllerDelegate { // MARK: - ProfileHeaderViewDelegate extension ProfileViewController: ProfileHeaderViewDelegate { - + func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) { guard let mastodonUser = viewModel.mastodonUser.value else { return } guard let avatar = imageView.image else { return } @@ -953,24 +961,24 @@ extension ProfileViewController: ProfileHeaderViewDelegate { } } } - - func profileHeaderView(_ profileHeaderView: ProfileHeaderView, activeLabel: ActiveLabel, entityDidPressed entity: ActiveEntity) { - switch entity.type { + + func profileHeaderView(_ profileHeaderView: ProfileHeaderView, metaTextView: MetaTextView, metaDidPressed meta: Meta) { + switch meta { case .url(_, _, let url, _): guard let url = URL(string: url) else { return } coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - case .mention(_, let userInfo): + case .mention(_, _, let userInfo): guard let href = userInfo?["href"] as? String, - let url = URL(string: href) else { return } + let url = URL(string: href) else { return } coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - case .hashtag(let hashtag, _): + case .hashtag(_, let hashtag, _): let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag) coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show) - default: + case .email, .emoji: break } } - + func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) { } diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 4b2809a3a..5efbaa684 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -10,6 +10,7 @@ import UIKit import Combine import CoreDataStack import MastodonSDK +import MastodonMeta // please override this base class class ProfileViewModel: NSObject { @@ -40,7 +41,7 @@ class ProfileViewModel: NSObject { let followingCount: CurrentValueSubject let followersCount: CurrentValueSubject let fields: CurrentValueSubject<[Mastodon.Entity.Field], Never> - let emojiDict: CurrentValueSubject + let emojiMeta: CurrentValueSubject // fulfill this before editing let accountForEdit = CurrentValueSubject(nil) @@ -83,7 +84,7 @@ class ProfileViewModel: NSObject { self.protected = CurrentValueSubject(mastodonUser?.locked) self.suspended = CurrentValueSubject(mastodonUser?.suspended ?? false) self.fields = CurrentValueSubject(mastodonUser?.fields ?? []) - self.emojiDict = CurrentValueSubject(mastodonUser?.emojiDict ?? [:]) + self.emojiMeta = CurrentValueSubject(mastodonUser?.emojiMeta ?? [:]) super.init() relationshipActionOptionSet @@ -258,7 +259,7 @@ extension ProfileViewModel { self.protected.value = mastodonUser?.locked self.suspended.value = mastodonUser?.suspended ?? false self.fields.value = mastodonUser?.fields ?? [] - self.emojiDict.value = mastodonUser?.emojiDict ?? [:] + self.emojiMeta.value = mastodonUser?.emojiMeta ?? [:] } private func update(mastodonUser: MastodonUser?, currentMastodonUser: MastodonUser?) { diff --git a/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift b/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift index 32d096a33..ff566a248 100644 --- a/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift +++ b/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift @@ -11,7 +11,6 @@ import AVKit import Combine import CoreData import CoreDataStack -import ActiveLabel import Meta import MetaTextKit @@ -212,9 +211,6 @@ extension ReportedStatusTableViewCell: StatusViewDelegate { func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) { } - - func statusView(_ statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - } func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) { } diff --git a/Mastodon/Scene/Search/Search/CollectionViewCell/SearchRecommendAccountsCollectionViewCell.swift b/Mastodon/Scene/Search/Search/CollectionViewCell/SearchRecommendAccountsCollectionViewCell.swift index b69914dbe..cd1d196e4 100644 --- a/Mastodon/Scene/Search/Search/CollectionViewCell/SearchRecommendAccountsCollectionViewCell.swift +++ b/Mastodon/Scene/Search/Search/CollectionViewCell/SearchRecommendAccountsCollectionViewCell.swift @@ -10,7 +10,8 @@ import CoreDataStack import Foundation import MastodonSDK import UIKit -import ActiveLabel +import MetaTextKit +import MastodonMeta protocol SearchRecommendAccountsCollectionViewCellDelegate: NSObject { func followButtonDidPressed(clickedUser: MastodonUser) @@ -43,14 +44,7 @@ class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell { let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) - let displayNameLabel: ActiveLabel = { - let label = ActiveLabel(style: .statusName) - label.textColor = .white - label.textAlignment = .center - label.font = .systemFont(ofSize: 18, weight: .semibold) - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() + let displayNameLabel = MetaLabel(style: .recommendAccountName) let acctLabel: UILabel = { let label = UILabel() @@ -165,7 +159,14 @@ extension SearchRecommendAccountsCollectionViewCell { } func config(with mastodonUser: MastodonUser) { - displayNameLabel.configure(content: mastodonUser.displayNameWithFallback, emojiDict: mastodonUser.emojiDict) + do { + let mastodonContent = MastodonContent(content: mastodonUser.displayNameWithFallback, emojis: mastodonUser.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + displayNameLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: mastodonUser.displayNameWithFallback) + displayNameLabel.configure(content: metaContent) + } acctLabel.text = "@" + mastodonUser.acct avatarImageView.af.setImage( withURL: URL(string: mastodonUser.avatar)!, diff --git a/Mastodon/Scene/Search/SearchDetail/TableViewCell/SearchResultTableViewCell.swift b/Mastodon/Scene/Search/SearchDetail/TableViewCell/SearchResultTableViewCell.swift index 1b48d1d2d..4c1753ff0 100644 --- a/Mastodon/Scene/Search/SearchDetail/TableViewCell/SearchResultTableViewCell.swift +++ b/Mastodon/Scene/Search/SearchDetail/TableViewCell/SearchResultTableViewCell.swift @@ -11,6 +11,8 @@ import Foundation import MastodonSDK import UIKit import FLAnimatedImage +import MetaTextKit +import MastodonMeta final class SearchResultTableViewCell: UITableViewCell { @@ -22,13 +24,7 @@ final class SearchResultTableViewCell: UITableViewCell { return imageView }() - let _titleLabel: UILabel = { - let label = UILabel() - label.textColor = Asset.Colors.brandBlue.color - label.font = .systemFont(ofSize: 17, weight: .semibold) - label.lineBreakMode = .byTruncatingTail - return label - }() + let _titleLabel = MetaLabel(style: .statusName) let _subTitleLabel: UILabel = { let label = UILabel() @@ -155,13 +151,28 @@ extension SearchResultTableViewCell { func config(with account: Mastodon.Entity.Account) { configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL())) - _titleLabel.text = account.displayName.isEmpty ? account.username : account.displayName + let name = account.displayName.isEmpty ? account.username : account.displayName + do { + let mastodonContent = MastodonContent(content: name, emojis: account.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + _titleLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: name) + _titleLabel.configure(content: metaContent) + } _subTitleLabel.text = account.acct } func config(with account: MastodonUser) { configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL())) - _titleLabel.text = account.displayNameWithFallback + do { + let mastodonContent = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojiMeta) + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + _titleLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: account.displayNameWithFallback) + _titleLabel.configure(content: metaContent) + } _subTitleLabel.text = account.acct } diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 744900bed..e8ff57844 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -8,10 +8,11 @@ import os.log import UIKit import Combine -import ActiveLabel import CoreData import CoreDataStack import MastodonSDK +import MetaTextKit +import MastodonMeta import AuthenticationServices class SettingsViewController: UIViewController, NeedsDependency { @@ -103,12 +104,7 @@ class SettingsViewController: UIViewController, NeedsDependency { return tableView }() - let tableFooterActiveLabel: ActiveLabel = { - let label = ActiveLabel(style: .default) - label.adjustsFontForContentSizeCategory = true - label.textAlignment = .center - return label - }() + let tableFooterLabel = MetaLabel(style: .settingTableFooter) lazy var tableFooterView: UIView = { // init with a frame to fix a conflict ('UIView-Encapsulated-Layout-Height' UIStackView:0x7ffe41e47da0.height == 0) let view = UIStackView(frame: CGRect(x: 0, y: 0, width: 320, height: 320)) @@ -117,8 +113,8 @@ class SettingsViewController: UIViewController, NeedsDependency { view.axis = .vertical view.alignment = .center - tableFooterActiveLabel.delegate = self - view.addArrangedSubview(tableFooterActiveLabel) + tableFooterLabel.linkDelegate = self + view.addArrangedSubview(tableFooterLabel) return view }() @@ -199,7 +195,15 @@ class SettingsViewController: UIViewController, NeedsDependency { let version = instance?.version ?? "-" let link = #"mastodon/mastodon"# let content = L10n.Scene.Settings.Footer.mastodonDescription(link, version) - self.tableFooterActiveLabel.configure(content: content, emojiDict: [:]) + let mastodonContent = MastodonContent(content: content, emojis: [:]) + do { + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + self.tableFooterLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: "") + self.tableFooterLabel.configure(content: metaContent) + assertionFailure() + } } .store(in: &disposeBag) } @@ -510,13 +514,16 @@ extension SettingsViewController: SettingsToggleCellDelegate { } } -extension SettingsViewController: ActiveLabelDelegate { - func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - coordinator.present( - scene: .safari(url: URL(string: "https://github.com/mastodon/mastodon")!), - from: self, - transition: .safariPresent(animated: true, completion: nil) - ) +// MARK: - MetaLabelDelegate +extension SettingsViewController: MetaLabelDelegate { + func metaLabel(_ metaLabel: MetaLabel, didSelectMeta meta: Meta) { + switch meta { + case .url(_, _, let url, _): + guard let url = URL(string: url) else { return } + coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil)) + default: + assertionFailure() + } } } diff --git a/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift b/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift index 33ef86dd0..d900307ab 100644 --- a/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift +++ b/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift @@ -6,19 +6,14 @@ // import UIKit -import ActiveLabel +import Meta +import MetaTextKit final class DoubleTitleLabelNavigationBarTitleView: UIView { let containerView = UIStackView() - let titleLabel: ActiveLabel = { - let label = ActiveLabel(style: .default) - label.font = .systemFont(ofSize: 17, weight: .semibold) - label.textColor = Asset.Colors.Label.primary.color - label.textAlignment = .center - return label - }() + let titleLabel = MetaLabel(style: .titleView) let subtitleLabel: UILabel = { let label = UILabel() @@ -58,9 +53,18 @@ extension DoubleTitleLabelNavigationBarTitleView { containerView.addArrangedSubview(titleLabel) containerView.addArrangedSubview(subtitleLabel) } + + func update(title: String, subtitle: String?) { + titleLabel.configure(content: PlaintextMetaContent(string: title)) + update(subtitle: subtitle) + } - func update(title: String, subtitle: String?, emojiDict: MastodonStatusContent.EmojiDict) { - titleLabel.configure(content: title, emojiDict: emojiDict) + func update(titleMetaContent: MetaContent, subtitle: String?) { + titleLabel.configure(content: titleMetaContent) + update(subtitle: subtitle) + } + + func update(subtitle: String?) { if let subtitle = subtitle { subtitleLabel.text = subtitle subtitleLabel.isHidden = false diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 0964ee3ba..c1294bff5 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -9,7 +9,6 @@ import os.log import UIKit import Combine import AVKit -import ActiveLabel import AlamofireImage import FLAnimatedImage import MetaTextKit @@ -26,7 +25,6 @@ protocol StatusViewDelegate: AnyObject { func statusView(_ statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) func statusView(_ statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) - func statusView(_ statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) } @@ -215,7 +213,7 @@ final class StatusView: UIView { metaText.textView.layer.masksToBounds = false metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment - let paragraphStyle: NSMutableParagraphStyle = { + metaText.paragraphStyle = { let style = NSMutableParagraphStyle() style.lineSpacing = 5 style.paragraphSpacing = 8 @@ -224,12 +222,10 @@ final class StatusView: UIView { metaText.textAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)), .foregroundColor: Asset.Colors.Label.primary.color, - .paragraphStyle: paragraphStyle, ] metaText.linkAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)), .foregroundColor: Asset.Colors.brandBlue.color, - .paragraphStyle: paragraphStyle, ] return metaText }() @@ -559,11 +555,10 @@ extension StatusView { // MARK: - MetaTextViewDelegate extension StatusView: MetaTextViewDelegate { - func metaTextView(_ metaTextView: MetaTextView, didSelectLink link: URL) { + func metaTextView(_ metaTextView: MetaTextView, didSelectMeta meta: Meta) { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") switch metaTextView { case contentMetaText.textView: - guard let meta = Meta(url: link) else { return } delegate?.statusView(self, metaText: contentMetaText, didSelectMeta: meta) default: assertionFailure() @@ -596,14 +591,6 @@ extension StatusView: UITextViewDelegate { } } -// MARK: - ActiveLabelDelegate -extension StatusView: ActiveLabelDelegate { - func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select entity: %s", ((#file as NSString).lastPathComponent), #line, #function, entity.primaryText) - delegate?.statusView(self, activeLabel: activeLabel, didSelectActiveEntity: entity) - } -} - // MARK: - ContentWarningOverlayViewDelegate extension StatusView: ContentWarningOverlayViewDelegate { func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) { diff --git a/Mastodon/Scene/Share/View/Node/Status/StatusNode.swift b/Mastodon/Scene/Share/View/Node/Status/StatusNode.swift index 9e62fd74d..693540473 100644 --- a/Mastodon/Scene/Share/View/Node/Status/StatusNode.swift +++ b/Mastodon/Scene/Share/View/Node/Status/StatusNode.swift @@ -11,11 +11,10 @@ import UIKit import Combine import AsyncDisplayKit import CoreDataStack -import ActiveLabel import func AVFoundation.AVMakeRect protocol StatusNodeDelegate: AnyObject { - func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType) + //func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType) } final class StatusNode: ASCellNode { @@ -29,21 +28,21 @@ final class StatusNode: ASCellNode { static let avatarImageSize = CGSize(width: 42, height: 42) static let avatarImageCornerRadius: CGFloat = 4 - static let statusContentAppearance: MastodonStatusContent.Appearance = { - let linkAttributes: [NSAttributedString.Key: Any] = [ - .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)), - .foregroundColor: Asset.Colors.brandBlue.color - ] - return MastodonStatusContent.Appearance( - attributes: [ - .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)), - .foregroundColor: Asset.Colors.Label.primary.color - ], - urlAttributes: linkAttributes, - hashtagAttributes: linkAttributes, - mentionAttributes: linkAttributes - ) - }() +// static let statusContentAppearance: MastodonStatusContent.Appearance = { +// let linkAttributes: [NSAttributedString.Key: Any] = [ +// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)), +// .foregroundColor: Asset.Colors.brandBlue.color +// ] +// return MastodonStatusContent.Appearance( +// attributes: [ +// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)), +// .foregroundColor: Asset.Colors.Label.primary.color +// ], +// urlAttributes: linkAttributes, +// hashtagAttributes: linkAttributes, +// mentionAttributes: linkAttributes +// ) +// }() let avatarImageNode: ASNetworkImageNode = { let node = ASNetworkImageNode() @@ -112,13 +111,14 @@ final class StatusNode: ASCellNode { .font: UIFont.systemFont(ofSize: 15, weight: .regular) ]) - statusContentTextNode.metaEditableTextNodeDelegate = self - if let parseResult = try? MastodonStatusContent.parse( - content: (status.reblog ?? status).content, - emojiDict: (status.reblog ?? status).emojiDict - ) { - statusContentTextNode.attributedText = parseResult.trimmedAttributedString(appearance: StatusNode.statusContentAppearance) - } + // FIXME: + // statusContentTextNode.metaEditableTextNodeDelegate = self +// if let parseResult = try? MastodonStatusContent.parse( +// content: (status.reblog ?? status).content, +// emojiDict: (status.reblog ?? status).emojiDict +// ) { +// statusContentTextNode.attributedText = parseResult.trimmedAttributedString(appearance: StatusNode.statusContentAppearance) +// } for imageNode in mediaMultiplexImageNodes { imageNode.dataSource = self @@ -200,29 +200,19 @@ final class StatusNode: ASCellNode { } -//extension StatusNode: ASImageDownloaderProtocol { -// func downloadImage(with URL: URL, callbackQueue: DispatchQueue, downloadProgress: ASImageDownloaderProgress?, completion: @escaping ASImageDownloaderCompletion) -> Any? { -// -// } -// -// func cancelImageDownload(forIdentifier downloadIdentifier: Any) { -// +// MARK: - ASEditableTextNodeDelegate +//extension StatusNode: ASMetaEditableTextNodeDelegate { +// func metaEditableTextNode(_ textNode: ASMetaEditableTextNode, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { +// guard let activityEntityType = ActiveEntityType(url: URL) else { +// return false +// } +// defer { +// delegate?.statusNode(self, statusContentTextNode: textNode, didSelectActiveEntityType: activityEntityType) +// } +// return false // } //} -// MARK: - ASEditableTextNodeDelegate -extension StatusNode: ASMetaEditableTextNodeDelegate { - func metaEditableTextNode(_ textNode: ASMetaEditableTextNode, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { - guard let activityEntityType = ActiveEntityType(url: URL) else { - return false - } - defer { - delegate?.statusNode(self, statusContentTextNode: textNode, didSelectActiveEntityType: activityEntityType) - } - return false - } -} - // MARK: - ASMultiplexImageNodeDataSource extension StatusNode: ASMultiplexImageNodeDataSource { func multiplexImageNode(_ imageNode: ASMultiplexImageNode, urlForImageIdentifier imageIdentifier: ASImageIdentifier) -> URL? { diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index 3a186f00b..8e9f4a641 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -11,7 +11,6 @@ import AVKit import Combine import CoreData import CoreDataStack -import ActiveLabel import Meta import MetaTextKit @@ -27,7 +26,6 @@ protocol StatusTableViewCellDelegate: AnyObject { func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton) - func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) @@ -328,10 +326,6 @@ extension StatusTableViewCell: StatusViewDelegate { func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) { delegate?.statusTableViewCell(self, statusView: statusView, pollVoteButtonPressed: button) } - - func statusView(_ statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { - delegate?.statusTableViewCell(self, statusView: statusView, activeLabel: activeLabel, didSelectActiveEntity: entity) - } func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) { delegate?.statusTableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta) diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift index 30f7688a6..905e1db32 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift @@ -11,7 +11,8 @@ import CoreDataStack import Foundation import MastodonSDK import UIKit -import ActiveLabel +import MetaTextKit +import MastodonMeta protocol SuggestionAccountTableViewCellDelegate: AnyObject { func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell) @@ -29,13 +30,7 @@ final class SuggestionAccountTableViewCell: UITableViewCell { return imageView }() - let titleLabel: ActiveLabel = { - let label = ActiveLabel(style: .statusName) - label.textColor = Asset.Colors.brandBlue.color - label.font = .systemFont(ofSize: 17, weight: .semibold) - label.lineBreakMode = .byTruncatingTail - return label - }() + let titleLabel = MetaLabel(style: .statusName) let subTitleLabel: UILabel = { let label = UILabel() @@ -152,8 +147,15 @@ extension SuggestionAccountTableViewCell { imageTransition: .crossDissolve(0.2) ) } - titleLabel.configure(content: account.displayNameWithFallback, emojiDict: account.emojiDict) - subTitleLabel.text = account.acct + let mastodonContent = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojiMeta) + do { + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + titleLabel.configure(content: metaContent) + } catch { + let metaContent = PlaintextMetaContent(string: account.displayNameWithFallback) + titleLabel.configure(content: metaContent) + } + subTitleLabel.text = "@" + account.acct button.isSelected = isSelected button.publisher(for: .touchUpInside) .sink { [weak self] _ in diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift index 71a7de9ee..c1d580a71 100644 --- a/Mastodon/Scene/Thread/ThreadViewController.swift +++ b/Mastodon/Scene/Thread/ThreadViewController.swift @@ -10,6 +10,7 @@ import UIKit import Combine import CoreData import AVKit +import MastodonMeta final class ThreadViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -84,18 +85,27 @@ extension ThreadViewController { tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - - viewModel.navigationBarTitle - .receive(on: DispatchQueue.main) - .sink { [weak self] tuple in - guard let self = self else { return } - guard let (title, emojiDict) = tuple else { - self.titleView.update(title: L10n.Scene.Thread.backTitle, subtitle: nil, emojiDict: [:]) - return - } - self.titleView.update(title: title, subtitle: nil, emojiDict: emojiDict) + + Publishers.CombineLatest( + viewModel.navigationBarTitle, + viewModel.navigationBarTitleEmojiMeta + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] title, emojiMeta in + guard let self = self else { return } + guard let title = title else { + self.titleView.update(title: "", subtitle: nil) + return } - .store(in: &disposeBag) + let mastodonContent = MastodonContent(content: title, emojis: emojiMeta ?? [:]) + do { + let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + self.titleView.update(titleMetaContent: metaContent, subtitle: nil) + } catch { + assertionFailure() + } + } + .store(in: &disposeBag) } override func viewWillAppear(_ animated: Bool) { diff --git a/Mastodon/Scene/Thread/ThreadViewModel.swift b/Mastodon/Scene/Thread/ThreadViewModel.swift index 26be56430..1c4a7071e 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel.swift @@ -12,6 +12,7 @@ import CoreData import CoreDataStack import GameplayKit import MastodonSDK +import MastodonMeta class ThreadViewModel { @@ -45,7 +46,8 @@ class ThreadViewModel { let ancestorItems = CurrentValueSubject<[Item], Never>([]) let descendantNodes = CurrentValueSubject<[LeafNode], Never>([]) let descendantItems = CurrentValueSubject<[Item], Never>([]) - let navigationBarTitle: CurrentValueSubject<(String, MastodonStatusContent.EmojiDict)?, Never> + let navigationBarTitle: CurrentValueSubject + let navigationBarTitleEmojiMeta: CurrentValueSubject init(context: AppContext, optionalStatus: Status?) { self.context = context @@ -53,8 +55,8 @@ class ThreadViewModel { self.rootItem = CurrentValueSubject(optionalStatus.flatMap { Item.root(statusObjectID: $0.objectID, attribute: Item.StatusAttribute()) }) self.existStatusFetchedResultsController = StatusFetchedResultsController(managedObjectContext: context.managedObjectContext, domain: nil, additionalTweetPredicate: nil) self.navigationBarTitle = CurrentValueSubject( - optionalStatus.flatMap { (L10n.Scene.Thread.title($0.author.displayNameWithFallback), $0.author.emojiDict) } - ) + optionalStatus.flatMap { L10n.Scene.Thread.title($0.author.displayNameWithFallback) }) + self.navigationBarTitleEmojiMeta = CurrentValueSubject(optionalStatus.flatMap { $0.author.emojiMeta } ?? [:]) // bind fetcher domain context.authenticationService.activeMastodonAuthenticationBox @@ -85,7 +87,8 @@ class ThreadViewModel { return } self.rootNode.value = RootNode(domain: status.domain, statusID: status.id, replyToID: status.inReplyToID) - self.navigationBarTitle.value = (L10n.Scene.Thread.title(status.author.displayNameWithFallback), status.author.emojiDict) + self.navigationBarTitle.value = L10n.Scene.Thread.title(status.author.displayNameWithFallback) + self.navigationBarTitleEmojiMeta.value = status.author.emojiMeta ?? [:] } } .store(in: &disposeBag) diff --git a/Mastodon/Service/StatusContentCacheService.swift b/Mastodon/Service/StatusContentCacheService.swift deleted file mode 100644 index ff17b0478..000000000 --- a/Mastodon/Service/StatusContentCacheService.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// StatusContentCacheService.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-6-17. -// - -import UIKit -import Combine - -final class StatusContentCacheService { - - var disposeBag = Set() - - let cache = NSCache() - - let workingQueue = DispatchQueue(label: "org.joinmastodon.app.BlurhashImageCacheService.working-queue", qos: .userInitiated, attributes: .concurrent) - - func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> MastodonStatusContent.ParseResult? { - let key = Key(content: content, emojiDict: emojiDict) - return cache.object(forKey: key)?.parseResult - } - - func prefetch(content: String, emojiDict: MastodonStatusContent.EmojiDict) { - let key = Key(content: content, emojiDict: emojiDict) - guard cache.object(forKey: key) == nil else { return } - MastodonStatusContent.parseResult(content: content, emojiDict: emojiDict) - .sink { [weak self] parseResult in - guard let self = self else { return } - guard let parseResult = parseResult else { return } - let wrapper = ParseResultWrapper(parseResult: parseResult) - self.cache.setObject(wrapper, forKey: key) - } - .store(in: &disposeBag) - } - -} - -extension StatusContentCacheService { - class Key: NSObject { - let content: String - let emojiDict: MastodonStatusContent.EmojiDict - - init(content: String, emojiDict: MastodonStatusContent.EmojiDict) { - self.content = content - self.emojiDict = emojiDict - } - - override func isEqual(_ object: Any?) -> Bool { - guard let object = object as? Key else { return false } - return object.content == content - && object.emojiDict == emojiDict - } - - override var hash: Int { - return content.hashValue ^ - emojiDict.hashValue - } - } - - class ParseResultWrapper: NSObject { - let parseResult: MastodonStatusContent.ParseResult - - init(parseResult: MastodonStatusContent.ParseResult) { - self.parseResult = parseResult - } - - override func isEqual(_ object: Any?) -> Bool { - guard let object = object as? ParseResultWrapper else { return false } - return object.parseResult == parseResult - } - - override var hash: Int { - return parseResult.hashValue - } - } -} diff --git a/Mastodon/State/AppContext.swift b/Mastodon/State/AppContext.swift index 7db669e5e..d4682ed5e 100644 --- a/Mastodon/State/AppContext.swift +++ b/Mastodon/State/AppContext.swift @@ -38,7 +38,6 @@ class AppContext: ObservableObject { let placeholderImageCacheService = PlaceholderImageCacheService() let blurhashImageCacheService = BlurhashImageCacheService() - let statusContentCacheService = StatusContentCacheService() let documentStore: DocumentStore private var documentStoreSubscription: AnyCancellable! From fff4f0a2049cecc9658723a65a24883f96b8e5c5 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 23 Jul 2021 19:33:05 +0800 Subject: [PATCH 4/5] feat: add emoji animation preference --- .../CoreData.xcdatamodel/contents | 5 +++-- CoreDataStack/Entity/Setting.swift | 7 +++++++ Localization/app.json | 7 +++---- Mastodon/Diffiable/Item/SettingsItem.swift | 6 ++++-- .../Section/Compose/CustomEmojiPickerSection.swift | 3 ++- Mastodon/Diffiable/Section/SettingsSection.swift | 2 ++ Mastodon/Extension/CoreDataStack/Emojis.swift | 6 +++++- .../MastodonSDK/Mastodon+Entity+Account.swift | 4 +++- Mastodon/Generated/Strings.swift | 12 ++++++------ Mastodon/Preference/AppearancePreference.swift | 9 +++++++++ Mastodon/Resources/ar.lproj/Localizable.strings | 5 +++-- Mastodon/Resources/en.lproj/Localizable.strings | 5 +++-- Mastodon/Scene/Settings/SettingsViewController.swift | 4 ++++ Mastodon/Scene/Settings/SettingsViewModel.swift | 8 +++----- Mastodon/Service/SettingService.swift | 6 ++++++ 15 files changed, 63 insertions(+), 26 deletions(-) diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents index 14c7dc2ec..51df98e36 100644 --- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents +++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -195,6 +195,7 @@ + @@ -290,7 +291,7 @@ - + diff --git a/CoreDataStack/Entity/Setting.swift b/CoreDataStack/Entity/Setting.swift index 0be80f973..27971157f 100644 --- a/CoreDataStack/Entity/Setting.swift +++ b/CoreDataStack/Entity/Setting.swift @@ -16,6 +16,7 @@ public final class Setting: NSManagedObject { @NSManaged public var appearanceRaw: String @NSManaged public var preferredTrueBlackDarkMode: Bool @NSManaged public var preferredStaticAvatar: Bool + @NSManaged public var preferredStaticEmoji: Bool @NSManaged public var preferredUsingDefaultBrowser: Bool @NSManaged public private(set) var createdAt: Date @@ -64,6 +65,12 @@ extension Setting { didUpdate(at: Date()) } + public func update(preferredStaticEmoji: Bool) { + guard preferredStaticEmoji != self.preferredStaticEmoji else { return } + self.preferredStaticEmoji = preferredStaticEmoji + didUpdate(at: Date()) + } + public func update(preferredUsingDefaultBrowser: Bool) { guard preferredUsingDefaultBrowser != self.preferredUsingDefaultBrowser else { return } self.preferredUsingDefaultBrowser = preferredUsingDefaultBrowser diff --git a/Localization/app.json b/Localization/app.json index 049094d4f..21e91ca29 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -500,10 +500,6 @@ "light": "Always Light", "dark": "Always Dark" }, - "appearance_settings": { - "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars" - }, "notifications": { "title": "Notifications", "favorites": "Favorites my post", @@ -520,6 +516,9 @@ }, "preference": { "title": "Preferences", + "true_black_dark_mode": "True black dark mode", + "disable_avatar_animation": "Disable animated avatars", + "disable_emoji_animation": "Disable animated emojis", "using_default_browser": "Use default browser to open links" }, "boring_zone": { diff --git a/Mastodon/Diffiable/Item/SettingsItem.swift b/Mastodon/Diffiable/Item/SettingsItem.swift index 777df750d..e82d0e1f9 100644 --- a/Mastodon/Diffiable/Item/SettingsItem.swift +++ b/Mastodon/Diffiable/Item/SettingsItem.swift @@ -43,12 +43,14 @@ extension SettingsItem { enum PreferenceType: CaseIterable { case darkMode case disableAvatarAnimation + case disableEmojiAnimation case useDefaultBrowser var title: String { switch self { - case .darkMode: return L10n.Scene.Settings.Section.AppearanceSettings.trueBlackDarkMode - case .disableAvatarAnimation: return L10n.Scene.Settings.Section.AppearanceSettings.disableAvatarAnimation + case .darkMode: return L10n.Scene.Settings.Section.Preference.trueBlackDarkMode + case .disableAvatarAnimation: return L10n.Scene.Settings.Section.Preference.disableAvatarAnimation + case .disableEmojiAnimation: return L10n.Scene.Settings.Section.Preference.disableEmojiAnimation case .useDefaultBrowser: return L10n.Scene.Settings.Section.Preference.usingDefaultBrowser } } diff --git a/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift b/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift index 251b52f3a..e55e8849d 100644 --- a/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift +++ b/Mastodon/Diffiable/Section/Compose/CustomEmojiPickerSection.swift @@ -24,7 +24,8 @@ extension CustomEmojiPickerSection { let placeholder = UIImage.placeholder(size: CustomEmojiPickerItemCollectionViewCell.itemSize, color: .systemFill) .af.imageRounded(withCornerRadius: 4) - let url = URL(string: attribute.emoji.url) + let isAnimated = !UserDefaults.shared.preferredStaticEmoji + let url = URL(string: isAnimated ? attribute.emoji.url : attribute.emoji.staticURL) cell.emojiImageView.sd_setImage( with: url, placeholderImage: placeholder, diff --git a/Mastodon/Diffiable/Section/SettingsSection.swift b/Mastodon/Diffiable/Section/SettingsSection.swift index e329bd120..939fd4315 100644 --- a/Mastodon/Diffiable/Section/SettingsSection.swift +++ b/Mastodon/Diffiable/Section/SettingsSection.swift @@ -125,6 +125,8 @@ extension SettingsSection { cell.switchButton.isOn = setting.preferredTrueBlackDarkMode case .disableAvatarAnimation: cell.switchButton.isOn = setting.preferredStaticAvatar + case .disableEmojiAnimation: + cell.switchButton.isOn = setting.preferredStaticEmoji case .useDefaultBrowser: cell.switchButton.isOn = setting.preferredUsingDefaultBrowser } diff --git a/Mastodon/Extension/CoreDataStack/Emojis.swift b/Mastodon/Extension/CoreDataStack/Emojis.swift index 2f45880bd..c318e8ed9 100644 --- a/Mastodon/Extension/CoreDataStack/Emojis.swift +++ b/Mastodon/Extension/CoreDataStack/Emojis.swift @@ -13,6 +13,8 @@ protocol EmojiContainer { var emojisData: Data? { get } } +// FIXME: `Mastodon.Entity.Account` extension + extension EmojiContainer { static func encode(emojis: [Mastodon.Entity.Emoji]) -> Data? { @@ -25,9 +27,11 @@ extension EmojiContainer { } var emojiMeta: MastodonContent.Emojis { + let isAnimated = !UserDefaults.shared.preferredStaticEmoji + var dict = MastodonContent.Emojis() for emoji in emojis ?? [] { - dict[emoji.shortcode] = emoji.url + dict[emoji.shortcode] = isAnimated ? emoji.url : emoji.staticURL } return dict } diff --git a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift index de703fd16..6aad350c3 100644 --- a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift +++ b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Account.swift @@ -32,9 +32,11 @@ extension Mastodon.Entity.Account { extension Mastodon.Entity.Account { var emojiMeta: MastodonContent.Emojis { + let isAnimated = !UserDefaults.shared.preferredStaticEmoji + var dict = MastodonContent.Emojis() for emoji in emojis ?? [] { - dict[emoji.shortcode] = emoji.url + dict[emoji.shortcode] = isAnimated ? emoji.url : emoji.staticURL } return dict } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 8d5443abb..0e31e5da7 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -943,12 +943,6 @@ internal enum L10n { /// Appearance internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Appearance.Title") } - internal enum AppearanceSettings { - /// Disable animated avatars - internal static let disableAvatarAnimation = L10n.tr("Localizable", "Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation") - /// True black dark mode - internal static let trueBlackDarkMode = L10n.tr("Localizable", "Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode") - } internal enum BoringZone { /// Account Settings internal static let accountSettings = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.AccountSettings") @@ -984,8 +978,14 @@ internal enum L10n { } } internal enum Preference { + /// Disable animated avatars + internal static let disableAvatarAnimation = L10n.tr("Localizable", "Scene.Settings.Section.Preference.DisableAvatarAnimation") + /// Disable animated emojis + internal static let disableEmojiAnimation = L10n.tr("Localizable", "Scene.Settings.Section.Preference.DisableEmojiAnimation") /// Preferences internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Preference.Title") + /// True black dark mode + internal static let trueBlackDarkMode = L10n.tr("Localizable", "Scene.Settings.Section.Preference.TrueBlackDarkMode") /// Use default browser to open links internal static let usingDefaultBrowser = L10n.tr("Localizable", "Scene.Settings.Section.Preference.UsingDefaultBrowser") } diff --git a/Mastodon/Preference/AppearancePreference.swift b/Mastodon/Preference/AppearancePreference.swift index 4630c1234..034bf9654 100644 --- a/Mastodon/Preference/AppearancePreference.swift +++ b/Mastodon/Preference/AppearancePreference.swift @@ -26,4 +26,13 @@ extension UserDefaults { set { self[#function] = newValue } } + @objc dynamic var preferredStaticEmoji: Bool { + get { + // default false + // without set register to profile timeline performance + return bool(forKey: #function) + } + set { self[#function] = newValue } + } + } diff --git a/Mastodon/Resources/ar.lproj/Localizable.strings b/Mastodon/Resources/ar.lproj/Localizable.strings index 99babb0bf..0d09e4594 100644 --- a/Mastodon/Resources/ar.lproj/Localizable.strings +++ b/Mastodon/Resources/ar.lproj/Localizable.strings @@ -321,8 +321,6 @@ any server."; "Scene.Settings.Section.Appearance.Dark" = "Always Dark"; "Scene.Settings.Section.Appearance.Light" = "Always Light"; "Scene.Settings.Section.Appearance.Title" = "Appearance"; -"Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation" = "Disable animated avatars"; -"Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode" = "True black dark mode"; "Scene.Settings.Section.BoringZone.AccountSettings" = "Account Settings"; "Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy"; "Scene.Settings.Section.BoringZone.Terms" = "Terms of Service"; @@ -337,7 +335,10 @@ any server."; "Scene.Settings.Section.Notifications.Trigger.Follower" = "a follower"; "Scene.Settings.Section.Notifications.Trigger.Noone" = "no one"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Disable animated avatars"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Disable animated emojis"; "Scene.Settings.Section.Preference.Title" = "Preferences"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "True black dark mode"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Use default browser to open links"; "Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache"; "Scene.Settings.Section.SpicyZone.Signout" = "Sign Out"; diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 99babb0bf..0d09e4594 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -321,8 +321,6 @@ any server."; "Scene.Settings.Section.Appearance.Dark" = "Always Dark"; "Scene.Settings.Section.Appearance.Light" = "Always Light"; "Scene.Settings.Section.Appearance.Title" = "Appearance"; -"Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation" = "Disable animated avatars"; -"Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode" = "True black dark mode"; "Scene.Settings.Section.BoringZone.AccountSettings" = "Account Settings"; "Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy"; "Scene.Settings.Section.BoringZone.Terms" = "Terms of Service"; @@ -337,7 +335,10 @@ any server."; "Scene.Settings.Section.Notifications.Trigger.Follower" = "a follower"; "Scene.Settings.Section.Notifications.Trigger.Noone" = "no one"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Disable animated avatars"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Disable animated emojis"; "Scene.Settings.Section.Preference.Title" = "Preferences"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "True black dark mode"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Use default browser to open links"; "Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache"; "Scene.Settings.Section.SpicyZone.Signout" = "Sign Out"; diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index e8ff57844..122a853ed 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -486,6 +486,8 @@ extension SettingsViewController: SettingsToggleCellDelegate { setting.update(preferredTrueBlackDarkMode: isOn) case .disableAvatarAnimation: setting.update(preferredStaticAvatar: isOn) + case .disableEmojiAnimation: + setting.update(preferredStaticEmoji: isOn) case .useDefaultBrowser: setting.update(preferredUsingDefaultBrowser: isOn) } @@ -498,6 +500,8 @@ extension SettingsViewController: SettingsToggleCellDelegate { ThemeService.shared.set(themeName: isOn ? .system : .mastodon) case .disableAvatarAnimation: UserDefaults.shared.preferredStaticAvatar = isOn + case .disableEmojiAnimation: + UserDefaults.shared.preferredStaticEmoji = isOn case .useDefaultBrowser: UserDefaults.shared.preferredUsingDefaultBrowser = isOn } diff --git a/Mastodon/Scene/Settings/SettingsViewModel.swift b/Mastodon/Scene/Settings/SettingsViewModel.swift index b586195e6..9bc0adce2 100644 --- a/Mastodon/Scene/Settings/SettingsViewModel.swift +++ b/Mastodon/Scene/Settings/SettingsViewModel.swift @@ -121,11 +121,9 @@ extension SettingsViewModel { // preference snapshot.appendSections([.preference]) - let preferenceItems: [SettingsItem] = [ - .preference(settingObjectID: setting.objectID, preferenceType: .darkMode), - .preference(settingObjectID: setting.objectID, preferenceType: .disableAvatarAnimation), - .preference(settingObjectID: setting.objectID, preferenceType: .useDefaultBrowser), - ] + let preferenceItems: [SettingsItem] = SettingsItem.PreferenceType.allCases.map { preferenceType in + SettingsItem.preference(settingObjectID: setting.objectID, preferenceType: preferenceType) + } snapshot.appendItems(preferenceItems,toSection: .preference) // boring zone diff --git a/Mastodon/Service/SettingService.swift b/Mastodon/Service/SettingService.swift index 37246765f..5985f6fce 100644 --- a/Mastodon/Service/SettingService.swift +++ b/Mastodon/Service/SettingService.swift @@ -211,9 +211,15 @@ extension SettingService { UserDefaults.shared.preferredStaticAvatar = setting.preferredStaticAvatar } + // set emoji mode + if UserDefaults.shared.preferredStaticEmoji != setting.preferredStaticEmoji { + UserDefaults.shared.preferredStaticEmoji = setting.preferredStaticEmoji + } + // set browser if UserDefaults.shared.preferredUsingDefaultBrowser != setting.preferredUsingDefaultBrowser { UserDefaults.shared.preferredUsingDefaultBrowser = setting.preferredUsingDefaultBrowser } + } } From ca930047d45f36c70da7e74d5602c514e8f6fa1a Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 23 Jul 2021 19:40:41 +0800 Subject: [PATCH 5/5] feat: add some haptic feedbacks for setting scene --- Mastodon/Scene/Settings/SettingsViewController.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 122a853ed..0685229c4 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -358,10 +358,11 @@ extension SettingsViewController: UITableViewDelegate { guard let dataSource = viewModel.dataSource else { return } guard let item = dataSource.itemIdentifier(for: indexPath) else { return } + let feedbackGenerator = UIImpactFeedbackGenerator(style: .light) + switch item { case .appearance: - // do nothing - break + feedbackGenerator.impactOccurred() case .notification: // do nothing break @@ -369,6 +370,7 @@ extension SettingsViewController: UITableViewDelegate { // do nothing break case .boringZone(let link), .spicyZone(let link): + feedbackGenerator.impactOccurred() switch link { case .accountSettings: guard let box = context.authenticationService.activeMastodonAuthenticationBox.value, @@ -399,6 +401,7 @@ extension SettingsViewController: UITableViewDelegate { } .store(in: &disposeBag) case .signOut: + feedbackGenerator.impactOccurred() alertToSignout() } }