Merge branch 'release/0.9.3'
This commit is contained in:
commit
f7a0df91a8
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20F71" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20G71" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Application" representedClassName=".Application" syncable="YES">
|
<entity name="Application" representedClassName=".Application" syncable="YES">
|
||||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||||
<attribute name="name" attributeType="String"/>
|
<attribute name="name" attributeType="String"/>
|
||||||
|
@ -195,6 +195,7 @@
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="domain" attributeType="String"/>
|
<attribute name="domain" attributeType="String"/>
|
||||||
<attribute name="preferredStaticAvatar" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="preferredStaticAvatar" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="preferredStaticEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
<attribute name="preferredTrueBlackDarkMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="preferredTrueBlackDarkMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
<attribute name="preferredUsingDefaultBrowser" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="preferredUsingDefaultBrowser" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
@ -290,7 +291,7 @@
|
||||||
<element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
|
<element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
|
||||||
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
|
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
|
||||||
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="149"/>
|
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="149"/>
|
||||||
<element name="Setting" positionX="72" positionY="162" width="128" height="164"/>
|
<element name="Setting" positionX="72" positionY="162" width="128" height="179"/>
|
||||||
<element name="Status" positionX="0" positionY="0" width="128" height="614"/>
|
<element name="Status" positionX="0" positionY="0" width="128" height="614"/>
|
||||||
<element name="Subscription" positionX="81" positionY="171" width="128" height="179"/>
|
<element name="Subscription" positionX="81" positionY="171" width="128" height="179"/>
|
||||||
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="14"/>
|
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="14"/>
|
||||||
|
|
|
@ -16,6 +16,7 @@ public final class Setting: NSManagedObject {
|
||||||
@NSManaged public var appearanceRaw: String
|
@NSManaged public var appearanceRaw: String
|
||||||
@NSManaged public var preferredTrueBlackDarkMode: Bool
|
@NSManaged public var preferredTrueBlackDarkMode: Bool
|
||||||
@NSManaged public var preferredStaticAvatar: Bool
|
@NSManaged public var preferredStaticAvatar: Bool
|
||||||
|
@NSManaged public var preferredStaticEmoji: Bool
|
||||||
@NSManaged public var preferredUsingDefaultBrowser: Bool
|
@NSManaged public var preferredUsingDefaultBrowser: Bool
|
||||||
|
|
||||||
@NSManaged public private(set) var createdAt: Date
|
@NSManaged public private(set) var createdAt: Date
|
||||||
|
@ -64,6 +65,12 @@ extension Setting {
|
||||||
didUpdate(at: Date())
|
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) {
|
public func update(preferredUsingDefaultBrowser: Bool) {
|
||||||
guard preferredUsingDefaultBrowser != self.preferredUsingDefaultBrowser else { return }
|
guard preferredUsingDefaultBrowser != self.preferredUsingDefaultBrowser else { return }
|
||||||
self.preferredUsingDefaultBrowser = preferredUsingDefaultBrowser
|
self.preferredUsingDefaultBrowser = preferredUsingDefaultBrowser
|
||||||
|
|
|
@ -500,10 +500,6 @@
|
||||||
"light": "Always Light",
|
"light": "Always Light",
|
||||||
"dark": "Always Dark"
|
"dark": "Always Dark"
|
||||||
},
|
},
|
||||||
"appearance_settings": {
|
|
||||||
"true_black_dark_mode": "True black dark mode",
|
|
||||||
"disable_avatar_animation": "Disable animated avatars"
|
|
||||||
},
|
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"title": "Notifications",
|
"title": "Notifications",
|
||||||
"favorites": "Favorites my post",
|
"favorites": "Favorites my post",
|
||||||
|
@ -520,6 +516,9 @@
|
||||||
},
|
},
|
||||||
"preference": {
|
"preference": {
|
||||||
"title": "Preferences",
|
"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"
|
"using_default_browser": "Use default browser to open links"
|
||||||
},
|
},
|
||||||
"boring_zone": {
|
"boring_zone": {
|
||||||
|
|
|
@ -65,7 +65,6 @@
|
||||||
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1FD25CD481700561493 /* StatusProvider.swift */; };
|
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1FD25CD481700561493 /* StatusProvider.swift */; };
|
||||||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; };
|
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; };
|
||||||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.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 */; };
|
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; };
|
||||||
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */; };
|
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */; };
|
||||||
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; };
|
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; };
|
||||||
|
@ -179,12 +178,12 @@
|
||||||
DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; };
|
DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; };
|
||||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
|
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
|
||||||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
|
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 */; };
|
DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */; };
|
||||||
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; };
|
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; };
|
||||||
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
|
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
|
||||||
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.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 */; };
|
DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; };
|
||||||
DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; };
|
DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; };
|
||||||
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; };
|
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; };
|
||||||
|
@ -196,6 +195,7 @@
|
||||||
DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */; };
|
DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */; };
|
||||||
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947126A7D2D70088FB11 /* AvatarButton.swift */; };
|
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947126A7D2D70088FB11 /* AvatarButton.swift */; };
|
||||||
DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.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 */; };
|
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; };
|
||||||
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
||||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
||||||
|
@ -227,8 +227,6 @@
|
||||||
DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; };
|
DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; };
|
||||||
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; };
|
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; };
|
||||||
DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; };
|
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 */; };
|
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; };
|
||||||
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; };
|
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; };
|
||||||
DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; };
|
DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; };
|
||||||
|
@ -310,7 +308,6 @@
|
||||||
DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
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 */; };
|
DB6804922637CD8700430867 /* AppName.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804912637CD8700430867 /* AppName.swift */; };
|
||||||
DB6804A52637CDCC00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; };
|
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 */; };
|
DB6804D12637CE4700430867 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804D02637CE4700430867 /* UserDefaults.swift */; };
|
||||||
DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804FC2637CFEC00430867 /* AppSecret.swift */; };
|
DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804FC2637CFEC00430867 /* AppSecret.swift */; };
|
||||||
DB6805102637D0F800430867 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB68050F2637D0F800430867 /* KeychainAccess */; };
|
DB6805102637D0F800430867 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB68050F2637D0F800430867 /* KeychainAccess */; };
|
||||||
|
@ -443,7 +440,6 @@
|
||||||
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
||||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
||||||
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.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 */; };
|
DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; };
|
||||||
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
||||||
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
||||||
|
@ -480,11 +476,7 @@
|
||||||
DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; };
|
DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; };
|
||||||
DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */; };
|
DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */; };
|
||||||
DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.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 */; };
|
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 */; };
|
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; };
|
||||||
DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; };
|
DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; };
|
||||||
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; };
|
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; };
|
||||||
|
@ -896,6 +888,7 @@
|
||||||
DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = "<group>"; };
|
DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = "<group>"; };
|
||||||
DB0C947126A7D2D70088FB11 /* AvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarButton.swift; sourceTree = "<group>"; };
|
DB0C947126A7D2D70088FB11 /* AvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarButton.swift; sourceTree = "<group>"; };
|
||||||
DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = "<group>"; };
|
DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = "<group>"; };
|
||||||
|
DB0E91E926A9675100BD2ACC /* MetaLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaLabel.swift; sourceTree = "<group>"; };
|
||||||
DB0F814D264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
DB0F814D264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -930,6 +923,7 @@
|
||||||
DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = "<group>"; };
|
DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = "<group>"; };
|
||||||
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
|
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
|
||||||
DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
DB3F693926AA97BD00C883AB /* MetaTextKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = MetaTextKit; path = ../MetaTextKit; sourceTree = "<group>"; };
|
||||||
DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
DB427DD525BAA00100D1B89D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
DB427DD725BAA00100D1B89D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
DB427DD725BAA00100D1B89D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1143,7 +1137,6 @@
|
||||||
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = "<group>"; };
|
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = "<group>"; };
|
||||||
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
|
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
|
||||||
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = "<group>"; };
|
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = "<group>"; };
|
||||||
DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentCacheService.swift; sourceTree = "<group>"; };
|
|
||||||
DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = "<group>"; };
|
DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = "<group>"; };
|
||||||
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = "<group>"; };
|
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = "<group>"; };
|
||||||
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1168,11 +1161,7 @@
|
||||||
DBBC24C326A544B900398BB9 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
DBBC24C326A544B900398BB9 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||||
DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = "<group>"; };
|
DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = "<group>"; };
|
||||||
DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = "<group>"; };
|
DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = "<group>"; };
|
||||||
DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonStatusContent.swift; sourceTree = "<group>"; };
|
|
||||||
DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = "<group>"; };
|
DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = "<group>"; };
|
||||||
DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+ParseResult.swift"; sourceTree = "<group>"; };
|
|
||||||
DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonField.swift; sourceTree = "<group>"; };
|
|
||||||
DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+Appearance.swift"; sourceTree = "<group>"; };
|
|
||||||
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
||||||
DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = "<group>"; };
|
DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = "<group>"; };
|
||||||
DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTableViewCell.swift; sourceTree = "<group>"; };
|
DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1257,10 +1246,8 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */,
|
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */,
|
||||||
DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */,
|
|
||||||
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
|
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
|
||||||
DBC6462B26A1738900B0E31B /* MastodonUI in Frameworks */,
|
DBC6462B26A1738900B0E31B /* MastodonUI in Frameworks */,
|
||||||
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */,
|
|
||||||
DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */,
|
DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */,
|
||||||
2D939AC825EE14620076FA61 /* CropViewController in Frameworks */,
|
2D939AC825EE14620076FA61 /* CropViewController in Frameworks */,
|
||||||
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */,
|
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */,
|
||||||
|
@ -1269,14 +1256,14 @@
|
||||||
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
|
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
|
||||||
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
|
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
|
||||||
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
||||||
|
DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */,
|
||||||
DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */,
|
DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */,
|
||||||
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
|
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
|
||||||
DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */,
|
DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */,
|
||||||
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */,
|
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */,
|
||||||
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */,
|
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */,
|
||||||
DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */,
|
|
||||||
DB03F7EB268976B5007B274C /* MastodonMeta in Frameworks */,
|
|
||||||
DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */,
|
DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */,
|
||||||
|
DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1329,8 +1316,6 @@
|
||||||
DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */,
|
DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */,
|
||||||
DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */,
|
DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */,
|
||||||
DBC6463726A195DB00B0E31B /* CoreDataStack.framework in Frameworks */,
|
DBC6463726A195DB00B0E31B /* CoreDataStack.framework in Frameworks */,
|
||||||
DB41ED8426A54D8A00F58330 /* MetaTextView in Frameworks */,
|
|
||||||
DB41ED8226A54D8A00F58330 /* MastodonMeta in Frameworks */,
|
|
||||||
DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */,
|
DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */,
|
||||||
DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */,
|
DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */,
|
||||||
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */,
|
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */,
|
||||||
|
@ -1602,7 +1587,6 @@
|
||||||
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */,
|
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */,
|
||||||
DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */,
|
DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */,
|
||||||
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */,
|
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */,
|
||||||
DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */,
|
|
||||||
DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */,
|
DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */,
|
||||||
);
|
);
|
||||||
path = Service;
|
path = Service;
|
||||||
|
@ -1976,6 +1960,7 @@
|
||||||
children = (
|
children = (
|
||||||
DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */,
|
DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */,
|
||||||
DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */,
|
DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */,
|
||||||
|
DB3F693926AA97BD00C883AB /* MetaTextKit */,
|
||||||
DB3D0FED25BAA42200EAA174 /* MastodonSDK */,
|
DB3D0FED25BAA42200EAA174 /* MastodonSDK */,
|
||||||
DB427DD425BAA00100D1B89D /* Mastodon */,
|
DB427DD425BAA00100D1B89D /* Mastodon */,
|
||||||
DB427DEB25BAA00100D1B89D /* MastodonTests */,
|
DB427DEB25BAA00100D1B89D /* MastodonTests */,
|
||||||
|
@ -2502,6 +2487,7 @@
|
||||||
DB6C8C0525F0921200AAA452 /* MastodonSDK */,
|
DB6C8C0525F0921200AAA452 /* MastodonSDK */,
|
||||||
DB44384E25E8C1FA008912A2 /* CALayer.swift */,
|
DB44384E25E8C1FA008912A2 /* CALayer.swift */,
|
||||||
2DF123A625C3B0210020F248 /* ActiveLabel.swift */,
|
2DF123A625C3B0210020F248 /* ActiveLabel.swift */,
|
||||||
|
DB0E91E926A9675100BD2ACC /* MetaLabel.swift */,
|
||||||
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */,
|
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */,
|
||||||
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */,
|
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */,
|
||||||
DB68A06225E905E000CFDF14 /* UIApplication.swift */,
|
DB68A06225E905E000CFDF14 /* UIApplication.swift */,
|
||||||
|
@ -2772,11 +2758,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */,
|
DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */,
|
||||||
DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */,
|
|
||||||
DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */,
|
DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */,
|
||||||
DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */,
|
|
||||||
DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */,
|
|
||||||
DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */,
|
|
||||||
DBFEF07626A691FB006D7ED1 /* MastodonAuthenticationBox.swift */,
|
DBFEF07626A691FB006D7ED1 /* MastodonAuthenticationBox.swift */,
|
||||||
);
|
);
|
||||||
path = Helper;
|
path = Helper;
|
||||||
|
@ -2989,7 +2971,6 @@
|
||||||
DB3D0FF225BAA61700EAA174 /* AlamofireImage */,
|
DB3D0FF225BAA61700EAA174 /* AlamofireImage */,
|
||||||
5D526FE125BE9AC400460CB9 /* MastodonSDK */,
|
5D526FE125BE9AC400460CB9 /* MastodonSDK */,
|
||||||
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */,
|
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */,
|
||||||
2D42FF6025C8177C004A627A /* ActiveLabel */,
|
|
||||||
DB0140BC25C40D7500F9F3CF /* CommonOSLog */,
|
DB0140BC25C40D7500F9F3CF /* CommonOSLog */,
|
||||||
2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */,
|
2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */,
|
||||||
2D939AC725EE14620076FA61 /* CropViewController */,
|
2D939AC725EE14620076FA61 /* CropViewController */,
|
||||||
|
@ -2999,9 +2980,9 @@
|
||||||
DBAC649D267DFE43007FE9FD /* DiffableDataSources */,
|
DBAC649D267DFE43007FE9FD /* DiffableDataSources */,
|
||||||
DBAC64A0267E6D02007FE9FD /* Fuzi */,
|
DBAC64A0267E6D02007FE9FD /* Fuzi */,
|
||||||
DBF7A0FB26830C33004176A2 /* FPSIndicator */,
|
DBF7A0FB26830C33004176A2 /* FPSIndicator */,
|
||||||
DB03F7EA268976B5007B274C /* MastodonMeta */,
|
|
||||||
DB03F7EC268976B5007B274C /* MetaTextView */,
|
|
||||||
DBC6462A26A1738900B0E31B /* MastodonUI */,
|
DBC6462A26A1738900B0E31B /* MastodonUI */,
|
||||||
|
DB01E23226A98F0900C3965B /* MastodonMeta */,
|
||||||
|
DB01E23426A98F0900C3965B /* MetaTextKit */,
|
||||||
);
|
);
|
||||||
productName = Mastodon;
|
productName = Mastodon;
|
||||||
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
||||||
|
@ -3127,8 +3108,6 @@
|
||||||
DBBC24A926A5301B00398BB9 /* MastodonSDK */,
|
DBBC24A926A5301B00398BB9 /* MastodonSDK */,
|
||||||
DBBC24B726A5421800398BB9 /* CommonOSLog */,
|
DBBC24B726A5421800398BB9 /* CommonOSLog */,
|
||||||
DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */,
|
DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */,
|
||||||
DB41ED8126A54D8A00F58330 /* MastodonMeta */,
|
|
||||||
DB41ED8326A54D8A00F58330 /* MetaTextView */,
|
|
||||||
DB0C946426A6FD4D0088FB11 /* AlamofireImage */,
|
DB0C946426A6FD4D0088FB11 /* AlamofireImage */,
|
||||||
);
|
);
|
||||||
productName = ShareActionExtension;
|
productName = ShareActionExtension;
|
||||||
|
@ -3166,7 +3145,7 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 1250;
|
LastSwiftUpdateCheck = 1250;
|
||||||
LastUpgradeCheck = 1240;
|
LastUpgradeCheck = 1250;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
DB427DD125BAA00100D1B89D = {
|
DB427DD125BAA00100D1B89D = {
|
||||||
CreatedOnToolsVersion = 12.4;
|
CreatedOnToolsVersion = 12.4;
|
||||||
|
@ -3213,20 +3192,18 @@
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */,
|
DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */,
|
||||||
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */,
|
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */,
|
||||||
2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */,
|
|
||||||
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */,
|
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */,
|
||||||
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */,
|
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */,
|
||||||
2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */,
|
2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */,
|
||||||
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */,
|
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */,
|
||||||
DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */,
|
DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */,
|
||||||
DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||||
DB6F5E30264E7410009108F4 /* XCRemoteSwiftPackageReference "TwitterTextEditor" */,
|
|
||||||
DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
||||||
DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */,
|
DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */,
|
||||||
DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */,
|
DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */,
|
||||||
DBF7A0FA26830C33004176A2 /* XCRemoteSwiftPackageReference "FPSIndicator" */,
|
DBF7A0FA26830C33004176A2 /* XCRemoteSwiftPackageReference "FPSIndicator" */,
|
||||||
DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */,
|
DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */,
|
||||||
DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */,
|
DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */,
|
||||||
);
|
);
|
||||||
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -3555,7 +3532,6 @@
|
||||||
DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */,
|
DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */,
|
||||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
||||||
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
||||||
DBAEDE61267B342D00D25FF5 /* StatusContentCacheService.swift in Sources */,
|
|
||||||
DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */,
|
DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */,
|
||||||
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||||
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
||||||
|
@ -3715,7 +3691,6 @@
|
||||||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||||
5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */,
|
5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */,
|
||||||
5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */,
|
5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */,
|
||||||
DBBC24DF26A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift in Sources */,
|
|
||||||
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */,
|
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */,
|
||||||
DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */,
|
DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */,
|
||||||
DBAC6488267D388B007FE9FD /* ASTableNode.swift in Sources */,
|
DBAC6488267D388B007FE9FD /* ASTableNode.swift in Sources */,
|
||||||
|
@ -3729,7 +3704,6 @@
|
||||||
DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */,
|
DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */,
|
||||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */,
|
2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */,
|
||||||
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */,
|
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */,
|
||||||
DBBC24E126A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift in Sources */,
|
|
||||||
DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */,
|
DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */,
|
||||||
2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */,
|
2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */,
|
||||||
DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */,
|
DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */,
|
||||||
|
@ -3752,6 +3726,7 @@
|
||||||
DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */,
|
DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */,
|
||||||
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
|
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
|
||||||
DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */,
|
DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */,
|
||||||
|
DB0E91EA26A9675100BD2ACC /* MetaLabel.swift in Sources */,
|
||||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
||||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
||||||
|
@ -3788,7 +3763,6 @@
|
||||||
DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */,
|
DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */,
|
||||||
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
|
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
|
||||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
||||||
DBBC24DD26A54BCB00398BB9 /* MastodonStatusContent.swift in Sources */,
|
|
||||||
2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */,
|
2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */,
|
||||||
DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */,
|
DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */,
|
||||||
DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */,
|
DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */,
|
||||||
|
@ -3840,7 +3814,6 @@
|
||||||
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
||||||
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */,
|
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */,
|
||||||
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
||||||
DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */,
|
|
||||||
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
||||||
DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */,
|
DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */,
|
||||||
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
|
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
|
||||||
|
@ -4308,7 +4281,7 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
|
@ -4316,7 +4289,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
@ -4335,7 +4308,7 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
|
@ -4343,7 +4316,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
@ -4600,7 +4573,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -4608,7 +4581,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -4624,7 +4597,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -4632,7 +4605,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -4648,7 +4621,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -4656,7 +4629,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -4672,7 +4645,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
INFOPLIST_FILE = ShareActionExtension/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -4680,7 +4653,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -4748,6 +4721,7 @@
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = ASDK;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = ASDK;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
name = "ASDK - Release";
|
name = "ASDK - Release";
|
||||||
|
@ -4761,7 +4735,7 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
|
@ -4769,7 +4743,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
@ -4876,7 +4850,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -4884,7 +4858,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -4995,7 +4969,7 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
|
@ -5003,7 +4977,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
@ -5110,7 +5084,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -5118,7 +5092,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -5164,7 +5138,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -5172,7 +5146,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -5187,7 +5161,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 43;
|
CURRENT_PROJECT_VERSION = 45;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -5195,7 +5169,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.9.2;
|
MARKETING_VERSION = 0.9.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -5309,14 +5283,6 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference 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" */ = {
|
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/vtourraine/ThirdPartyMailer.git";
|
repositoryURL = "https://github.com/vtourraine/ThirdPartyMailer.git";
|
||||||
|
@ -5349,12 +5315,12 @@
|
||||||
minimumVersion = 0.1.1;
|
minimumVersion = 0.1.1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */ = {
|
DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/TwidereProject/MetaTextView.git";
|
repositoryURL = "https://github.com/TwidereProject/MetaTextKit.git";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 1.4.0;
|
version = 2.1.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = {
|
DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = {
|
||||||
|
@ -5381,14 +5347,6 @@
|
||||||
minimumVersion = 4.2.2;
|
minimumVersion = 4.2.2;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
DB6F5E30264E7410009108F4 /* XCRemoteSwiftPackageReference "TwitterTextEditor" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/MainasuK/TwitterTextEditor.git";
|
|
||||||
requirement = {
|
|
||||||
branch = "feature/expose-layout";
|
|
||||||
kind = branch;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */ = {
|
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/MainasuK/UITextView-Placeholder";
|
repositoryURL = "https://github.com/MainasuK/UITextView-Placeholder";
|
||||||
|
@ -5440,11 +5398,6 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
2D42FF6025C8177C004A627A /* ActiveLabel */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */;
|
|
||||||
productName = ActiveLabel;
|
|
||||||
};
|
|
||||||
2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */ = {
|
2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */;
|
package = 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */;
|
||||||
|
@ -5474,15 +5427,15 @@
|
||||||
package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */;
|
package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */;
|
||||||
productName = CommonOSLog;
|
productName = CommonOSLog;
|
||||||
};
|
};
|
||||||
DB03F7EA268976B5007B274C /* MastodonMeta */ = {
|
DB01E23226A98F0900C3965B /* MastodonMeta */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */;
|
package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */;
|
||||||
productName = MastodonMeta;
|
productName = MastodonMeta;
|
||||||
};
|
};
|
||||||
DB03F7EC268976B5007B274C /* MetaTextView */ = {
|
DB01E23426A98F0900C3965B /* MetaTextKit */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */;
|
package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */;
|
||||||
productName = MetaTextView;
|
productName = MetaTextKit;
|
||||||
};
|
};
|
||||||
DB0C946426A6FD4D0088FB11 /* AlamofireImage */ = {
|
DB0C946426A6FD4D0088FB11 /* AlamofireImage */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
@ -5494,16 +5447,6 @@
|
||||||
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
||||||
productName = 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 */ = {
|
DB68050F2637D0F800430867 /* KeychainAccess */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||||
|
|
|
@ -37,12 +37,12 @@
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>23</integer>
|
<integer>24</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>21</integer>
|
<integer>23</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -1,22 +1,13 @@
|
||||||
{
|
{
|
||||||
"object": {
|
"object": {
|
||||||
"pins": [
|
"pins": [
|
||||||
{
|
|
||||||
"package": "ActiveLabel",
|
|
||||||
"repositoryURL": "https://github.com/TwidereProject/ActiveLabel.swift",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "d503eb3bfabc54a70139618ab2ba09ebb8c09672",
|
|
||||||
"version": "5.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "Alamofire",
|
"package": "Alamofire",
|
||||||
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "4d19ad82f80cc71ff829b941ded114c56f4f604c",
|
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae",
|
||||||
"version": "5.4.2"
|
"version": "5.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -100,24 +91,6 @@
|
||||||
"version": "4.2.2"
|
"version": "4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"package": "Kingfisher",
|
|
||||||
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "44450a8f564d7c0165f736ba2250649ff8d3e556",
|
|
||||||
"version": "6.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "MetaTextView",
|
|
||||||
"repositoryURL": "https://github.com/TwidereProject/MetaTextView.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "4c16bc639652a7e1bff4f75e1eba2fcf40213974",
|
|
||||||
"version": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "Nuke",
|
"package": "Nuke",
|
||||||
"repositoryURL": "https://github.com/kean/Nuke.git",
|
"repositoryURL": "https://github.com/kean/Nuke.git",
|
||||||
|
@ -145,6 +118,15 @@
|
||||||
"version": "3.6.2"
|
"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",
|
"package": "swift-nio",
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||||
|
@ -208,15 +190,6 @@
|
||||||
"version": "2.6.0"
|
"version": "2.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"package": "TwitterTextEditor",
|
|
||||||
"repositoryURL": "https://github.com/MainasuK/TwitterTextEditor.git",
|
|
||||||
"state": {
|
|
||||||
"branch": "feature/expose-layout",
|
|
||||||
"revision": "c208329b23dcb3c8c7192de34776440d625a26a4",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "UITextView+Placeholder",
|
"package": "UITextView+Placeholder",
|
||||||
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder",
|
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
/// Note: update Equatable when change case
|
/// Note: update Equatable when change case
|
||||||
enum ComposeStatusItem {
|
enum ComposeStatusItem {
|
||||||
|
@ -25,7 +26,7 @@ extension ComposeStatusItem {
|
||||||
|
|
||||||
let avatarURL = CurrentValueSubject<URL?, Never>(nil)
|
let avatarURL = CurrentValueSubject<URL?, Never>(nil)
|
||||||
let displayName = CurrentValueSubject<String?, Never>(nil)
|
let displayName = CurrentValueSubject<String?, Never>(nil)
|
||||||
let emojiDict = CurrentValueSubject<MastodonStatusContent.EmojiDict, Never>([:])
|
let emojiMeta = CurrentValueSubject<MastodonContent.Emojis, Never>([:])
|
||||||
let username = CurrentValueSubject<String?, Never>(nil)
|
let username = CurrentValueSubject<String?, Never>(nil)
|
||||||
let composeContent = CurrentValueSubject<String?, Never>(nil)
|
let composeContent = CurrentValueSubject<String?, Never>(nil)
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ extension ComposeStatusItem {
|
||||||
static func == (lhs: ComposeStatusAttribute, rhs: ComposeStatusAttribute) -> Bool {
|
static func == (lhs: ComposeStatusAttribute, rhs: ComposeStatusAttribute) -> Bool {
|
||||||
return lhs.avatarURL.value == rhs.avatarURL.value &&
|
return lhs.avatarURL.value == rhs.avatarURL.value &&
|
||||||
lhs.displayName.value == rhs.displayName.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.username.value == rhs.username.value &&
|
||||||
lhs.composeContent.value == rhs.composeContent.value &&
|
lhs.composeContent.value == rhs.composeContent.value &&
|
||||||
lhs.isContentWarningComposing.value == rhs.isContentWarningComposing.value &&
|
lhs.isContentWarningComposing.value == rhs.isContentWarningComposing.value &&
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
enum ProfileFieldItem {
|
enum ProfileFieldItem {
|
||||||
case field(field: FieldValue, attribute: FieldItemAttribute)
|
case field(field: FieldValue, attribute: FieldItemAttribute)
|
||||||
|
@ -56,7 +57,7 @@ extension ProfileFieldItem {
|
||||||
|
|
||||||
extension ProfileFieldItem {
|
extension ProfileFieldItem {
|
||||||
class FieldItemAttribute: Equatable, ProfileFieldListSeparatorLineConfigurable {
|
class FieldItemAttribute: Equatable, ProfileFieldListSeparatorLineConfigurable {
|
||||||
let emojiDict = CurrentValueSubject<MastodonStatusContent.EmojiDict, Never>([:])
|
let emojiMeta = CurrentValueSubject<MastodonContent.Emojis, Never>([:])
|
||||||
|
|
||||||
var isEditing = false
|
var isEditing = false
|
||||||
var isLast = false
|
var isLast = false
|
||||||
|
|
|
@ -43,12 +43,14 @@ extension SettingsItem {
|
||||||
enum PreferenceType: CaseIterable {
|
enum PreferenceType: CaseIterable {
|
||||||
case darkMode
|
case darkMode
|
||||||
case disableAvatarAnimation
|
case disableAvatarAnimation
|
||||||
|
case disableEmojiAnimation
|
||||||
case useDefaultBrowser
|
case useDefaultBrowser
|
||||||
|
|
||||||
var title: String {
|
var title: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .darkMode: return L10n.Scene.Settings.Section.AppearanceSettings.trueBlackDarkMode
|
case .darkMode: return L10n.Scene.Settings.Section.Preference.trueBlackDarkMode
|
||||||
case .disableAvatarAnimation: return L10n.Scene.Settings.Section.AppearanceSettings.disableAvatarAnimation
|
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
|
case .useDefaultBrowser: return L10n.Scene.Settings.Section.Preference.usingDefaultBrowser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
|
|
||||||
|
@ -45,12 +45,19 @@ extension ComposeStatusSection {
|
||||||
// set display name and username
|
// set display name and username
|
||||||
Publishers.CombineLatest3(
|
Publishers.CombineLatest3(
|
||||||
attribute.displayName,
|
attribute.displayName,
|
||||||
attribute.emojiDict,
|
attribute.emojiMeta,
|
||||||
attribute.username.eraseToAnyPublisher()
|
attribute.username
|
||||||
)
|
)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { displayName, emojiDict, username in
|
.sink { displayName, emojiMeta, username in
|
||||||
cell.statusView.nameLabel.configure(content: displayName ?? " ", emojiDict: emojiDict)
|
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 } ?? " "
|
cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " "
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
|
|
|
@ -24,8 +24,14 @@ extension CustomEmojiPickerSection {
|
||||||
let placeholder = UIImage.placeholder(size: CustomEmojiPickerItemCollectionViewCell.itemSize, color: .systemFill)
|
let placeholder = UIImage.placeholder(size: CustomEmojiPickerItemCollectionViewCell.itemSize, color: .systemFill)
|
||||||
.af.imageRounded(withCornerRadius: 4)
|
.af.imageRounded(withCornerRadius: 4)
|
||||||
|
|
||||||
let url = URL(string: attribute.emoji.url)
|
let isAnimated = !UserDefaults.shared.preferredStaticEmoji
|
||||||
cell.emojiImageView.setImage(url: url, placeholder: placeholder, scaleToSize: CustomEmojiPickerItemCollectionViewCell.itemSize)
|
let url = URL(string: isAnimated ? attribute.emoji.url : attribute.emoji.staticURL)
|
||||||
|
cell.emojiImageView.sd_setImage(
|
||||||
|
with: url,
|
||||||
|
placeholderImage: placeholder,
|
||||||
|
options: [],
|
||||||
|
context: nil
|
||||||
|
)
|
||||||
cell.accessibilityLabel = attribute.emoji.shortcode
|
cell.accessibilityLabel = attribute.emoji.shortcode
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import os
|
import os
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
enum ProfileFieldSection: Equatable, Hashable {
|
enum ProfileFieldSection: Equatable, Hashable {
|
||||||
case main
|
case main
|
||||||
|
@ -29,32 +30,60 @@ extension ProfileFieldSection {
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ProfileFieldCollectionViewCell.self), for: indexPath) as! ProfileFieldCollectionViewCell
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ProfileFieldCollectionViewCell.self), for: indexPath) as! ProfileFieldCollectionViewCell
|
||||||
|
|
||||||
// set key
|
// 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
|
cell.fieldView.titleTextField.text = field.name.value
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
field.name.removeDuplicates(),
|
field.name.removeDuplicates(),
|
||||||
attribute.emojiDict.removeDuplicates()
|
attribute.emojiMeta.removeDuplicates()
|
||||||
)
|
)
|
||||||
.receive(on: RunLoop.main)
|
.receive(on: RunLoop.main)
|
||||||
.sink { [weak cell] name, emojiDict in
|
.sink { [weak cell] name, emojiMeta in
|
||||||
guard let cell = cell else { return }
|
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
|
// only bind label. The text field should only set once
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
|
|
||||||
// set value
|
// 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
|
cell.fieldView.valueTextField.text = field.value.value
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
field.value.removeDuplicates(),
|
field.value.removeDuplicates(),
|
||||||
attribute.emojiDict.removeDuplicates()
|
attribute.emojiMeta.removeDuplicates()
|
||||||
)
|
)
|
||||||
.receive(on: RunLoop.main)
|
.receive(on: RunLoop.main)
|
||||||
.sink { [weak cell] value, emojiDict in
|
.sink { [weak cell] value, emojiMeta in
|
||||||
guard let cell = cell else { return }
|
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
|
// only bind label. The text field should only set once
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
|
@ -76,8 +105,8 @@ extension ProfileFieldSection {
|
||||||
// setup editing state
|
// setup editing state
|
||||||
cell.fieldView.titleTextField.isHidden = !attribute.isEditing
|
cell.fieldView.titleTextField.isHidden = !attribute.isEditing
|
||||||
cell.fieldView.valueTextField.isHidden = !attribute.isEditing
|
cell.fieldView.valueTextField.isHidden = !attribute.isEditing
|
||||||
cell.fieldView.titleActiveLabel.isHidden = attribute.isEditing
|
cell.fieldView.titleMetaLabel.isHidden = attribute.isEditing
|
||||||
cell.fieldView.valueActiveLabel.isHidden = attribute.isEditing
|
cell.fieldView.valueMetaLabel.isHidden = attribute.isEditing
|
||||||
|
|
||||||
// set control hidden
|
// set control hidden
|
||||||
let isHidden = !attribute.isEditing
|
let isHidden = !attribute.isEditing
|
||||||
|
|
|
@ -125,6 +125,8 @@ extension SettingsSection {
|
||||||
cell.switchButton.isOn = setting.preferredTrueBlackDarkMode
|
cell.switchButton.isOn = setting.preferredTrueBlackDarkMode
|
||||||
case .disableAvatarAnimation:
|
case .disableAvatarAnimation:
|
||||||
cell.switchButton.isOn = setting.preferredStaticAvatar
|
cell.switchButton.isOn = setting.preferredStaticAvatar
|
||||||
|
case .disableEmojiAnimation:
|
||||||
|
cell.switchButton.isOn = setting.preferredStaticEmoji
|
||||||
case .useDefaultBrowser:
|
case .useDefaultBrowser:
|
||||||
cell.switchButton.isOn = setting.preferredUsingDefaultBrowser
|
cell.switchButton.isOn = setting.preferredUsingDefaultBrowser
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import CoreDataStack
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import MetaTextKit
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
enum NotificationSection: Equatable, Hashable {
|
enum NotificationSection: Equatable, Hashable {
|
||||||
case main
|
case main
|
||||||
|
@ -66,7 +68,14 @@ extension NotificationSection {
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
// configure author name, notification description, timestamp
|
// 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 createAt = notification.createAt
|
||||||
let actionText = notification.notificationType.actionText
|
let actionText = notification.notificationType.actionText
|
||||||
cell.actionLabel.text = actionText + " · " + createAt.timeAgoSinceNow
|
cell.actionLabel.text = actionText + " · " + createAt.timeAgoSinceNow
|
||||||
|
|
|
@ -113,7 +113,7 @@ extension StatusSection {
|
||||||
accessibilityViews.append(cell.statusView.headerInfoLabel)
|
accessibilityViews.append(cell.statusView.headerInfoLabel)
|
||||||
}
|
}
|
||||||
accessibilityViews.append(contentsOf: [
|
accessibilityViews.append(contentsOf: [
|
||||||
cell.statusView.nameLabel,
|
cell.statusView.nameMetaLabel,
|
||||||
cell.statusView.dateLabel,
|
cell.statusView.dateLabel,
|
||||||
cell.statusView.contentMetaText.textView,
|
cell.statusView.contentMetaText.textView,
|
||||||
])
|
])
|
||||||
|
@ -171,7 +171,7 @@ extension StatusSection {
|
||||||
cell.statusView.contentMetaText.textView.isAccessibilityElement = false
|
cell.statusView.contentMetaText.textView.isAccessibilityElement = false
|
||||||
var accessibilityElements: [Any] = []
|
var accessibilityElements: [Any] = []
|
||||||
accessibilityElements.append(cell.statusView.avatarView)
|
accessibilityElements.append(cell.statusView.avatarView)
|
||||||
accessibilityElements.append(cell.statusView.nameLabel)
|
accessibilityElements.append(cell.statusView.nameMetaLabel)
|
||||||
accessibilityElements.append(cell.statusView.dateLabel)
|
accessibilityElements.append(cell.statusView.dateLabel)
|
||||||
// TODO: a11y
|
// TODO: a11y
|
||||||
accessibilityElements.append(cell.statusView.contentMetaText.textView)
|
accessibilityElements.append(cell.statusView.contentMetaText.textView)
|
||||||
|
@ -652,7 +652,13 @@ extension StatusSection {
|
||||||
return L10n.Common.Controls.Status.userReblogged(name)
|
return L10n.Common.Controls.Status.userReblogged(name)
|
||||||
}()
|
}()
|
||||||
// sync set display name to avoid layout issue
|
// 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.accessibilityLabel = headerText
|
||||||
cell.statusView.headerInfoLabel.isAccessibilityElement = true
|
cell.statusView.headerInfoLabel.isAccessibilityElement = true
|
||||||
} else if status.inReplyToID != nil {
|
} else if status.inReplyToID != nil {
|
||||||
|
@ -666,7 +672,13 @@ extension StatusSection {
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||||
return L10n.Common.Controls.Status.userRepliedTo(name)
|
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.accessibilityLabel = headerText
|
||||||
cell.statusView.headerInfoLabel.isAccessibilityElement = status.replyTo != nil
|
cell.statusView.headerInfoLabel.isAccessibilityElement = status.replyTo != nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -682,8 +694,15 @@ extension StatusSection {
|
||||||
// name
|
// name
|
||||||
let author = (status.reblog ?? status).author
|
let author = (status.reblog ?? status).author
|
||||||
let nameContent = author.displayNameWithFallback
|
let nameContent = author.displayNameWithFallback
|
||||||
cell.statusView.nameLabel.configure(content: nameContent, emojiDict: author.emojiDict)
|
do {
|
||||||
cell.statusView.nameLabel.accessibilityLabel = nameContent
|
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
|
// username
|
||||||
cell.statusView.usernameLabel.text = "@" + author.acct
|
cell.statusView.usernameLabel.text = "@" + author.acct
|
||||||
// avatar
|
// avatar
|
||||||
|
@ -1016,6 +1035,7 @@ extension StatusSection {
|
||||||
}
|
}
|
||||||
snapshot.appendItems(pollItems, toSection: .main)
|
snapshot.appendItems(pollItems, toSection: .main)
|
||||||
cell.statusView.pollTableViewDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
cell.statusView.pollTableViewDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||||
|
cell.statusView.pollTableViewHeightLayoutConstraint.constant = PollOptionTableViewCell.height * CGFloat(poll.options.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func configureActionToolBar(
|
static func configureActionToolBar(
|
||||||
|
|
|
@ -1,173 +1,66 @@
|
||||||
|
//extension ActiveEntity {
|
||||||
//
|
//
|
||||||
// ActiveLabel.swift
|
// var accessibilityLabelDescription: String {
|
||||||
// Mastodon
|
// 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
|
//final class ActiveLabelAccessibilityElement: UIAccessibilityElement {
|
||||||
import Foundation
|
// var index: Int!
|
||||||
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!
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - UIAccessibilityContainer
|
// MARK: - UIAccessibilityContainer
|
||||||
extension ActiveLabel {
|
//extension ActiveLabel {
|
||||||
|
//
|
||||||
func createAccessibilityElements() -> [UIAccessibilityElement] {
|
// func createAccessibilityElements() -> [UIAccessibilityElement] {
|
||||||
var elements: [UIAccessibilityElement] = []
|
// var elements: [UIAccessibilityElement] = []
|
||||||
|
//
|
||||||
let element = ActiveLabelAccessibilityElement(accessibilityContainer: self)
|
// let element = ActiveLabelAccessibilityElement(accessibilityContainer: self)
|
||||||
element.accessibilityTraits = .staticText
|
// element.accessibilityTraits = .staticText
|
||||||
element.accessibilityLabel = accessibilityLabel
|
// element.accessibilityLabel = accessibilityLabel
|
||||||
element.accessibilityFrame = superview!.convert(frame, to: nil)
|
// element.accessibilityFrame = superview!.convert(frame, to: nil)
|
||||||
element.accessibilityLanguage = accessibilityLanguage
|
// element.accessibilityLanguage = accessibilityLanguage
|
||||||
elements.append(element)
|
// elements.append(element)
|
||||||
|
//
|
||||||
for entity in activeEntities {
|
// for entity in activeEntities {
|
||||||
guard let element = entity.accessibilityElement(in: self) else { continue }
|
// guard let element = entity.accessibilityElement(in: self) else { continue }
|
||||||
var glyphRange = NSRange()
|
// var glyphRange = NSRange()
|
||||||
layoutManager.characterRange(forGlyphRange: entity.range, actualGlyphRange: &glyphRange)
|
// layoutManager.characterRange(forGlyphRange: entity.range, actualGlyphRange: &glyphRange)
|
||||||
let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
// let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
||||||
element.accessibilityFrame = self.convert(rect, to: nil)
|
// element.accessibilityFrame = self.convert(rect, to: nil)
|
||||||
element.accessibilityContainer = self
|
// element.accessibilityContainer = self
|
||||||
elements.append(element)
|
// elements.append(element)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return elements
|
// return elements
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|
|
@ -13,6 +13,8 @@ protocol EmojiContainer {
|
||||||
var emojisData: Data? { get }
|
var emojisData: Data? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: `Mastodon.Entity.Account` extension
|
||||||
|
|
||||||
extension EmojiContainer {
|
extension EmojiContainer {
|
||||||
|
|
||||||
static func encode(emojis: [Mastodon.Entity.Emoji]) -> Data? {
|
static func encode(emojis: [Mastodon.Entity.Emoji]) -> Data? {
|
||||||
|
@ -23,20 +25,13 @@ extension EmojiContainer {
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
return emojisData.flatMap { try? decoder.decode([Mastodon.Entity.Emoji].self, from: $0) }
|
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 emojiMeta: MastodonContent.Emojis {
|
||||||
|
let isAnimated = !UserDefaults.shared.preferredStaticEmoji
|
||||||
|
|
||||||
var dict = MastodonContent.Emojis()
|
var dict = MastodonContent.Emojis()
|
||||||
for emoji in emojis ?? [] {
|
for emoji in emojis ?? [] {
|
||||||
dict[emoji.shortcode] = emoji.url
|
dict[emoji.shortcode] = isAnimated ? emoji.url : emoji.staticURL
|
||||||
}
|
}
|
||||||
return dict
|
return dict
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
extension Mastodon.Entity.Account: Hashable {
|
extension Mastodon.Entity.Account: Hashable {
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
@ -28,3 +29,15 @@ extension Mastodon.Entity.Account {
|
||||||
return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")!
|
return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Mastodon.Entity.Account {
|
||||||
|
var emojiMeta: MastodonContent.Emojis {
|
||||||
|
let isAnimated = !UserDefaults.shared.preferredStaticEmoji
|
||||||
|
|
||||||
|
var dict = MastodonContent.Emojis()
|
||||||
|
for emoji in emojis ?? [] {
|
||||||
|
dict[emoji.shortcode] = isAnimated ? emoji.url : emoji.staticURL
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
//
|
||||||
|
// MetaText.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-7-22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Meta
|
||||||
|
import MetaTextKit
|
||||||
|
|
||||||
|
extension MetaLabel {
|
||||||
|
enum Style {
|
||||||
|
case statusHeader
|
||||||
|
case statusName
|
||||||
|
case notificationName
|
||||||
|
case profileFieldName
|
||||||
|
case profileFieldValue
|
||||||
|
case recommendAccountName
|
||||||
|
case titleView
|
||||||
|
case settingTableFooter
|
||||||
|
}
|
||||||
|
|
||||||
|
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 .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
|
||||||
|
self.textColor = textColor
|
||||||
|
|
||||||
|
textAttributes = [
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
}
|
|
@ -943,12 +943,6 @@ internal enum L10n {
|
||||||
/// Appearance
|
/// Appearance
|
||||||
internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Appearance.Title")
|
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 {
|
internal enum BoringZone {
|
||||||
/// Account Settings
|
/// Account Settings
|
||||||
internal static let accountSettings = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.AccountSettings")
|
internal static let accountSettings = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.AccountSettings")
|
||||||
|
@ -984,8 +978,14 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Preference {
|
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
|
/// Preferences
|
||||||
internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Preference.Title")
|
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
|
/// Use default browser to open links
|
||||||
internal static let usingDefaultBrowser = L10n.tr("Localizable", "Scene.Settings.Section.Preference.UsingDefaultBrowser")
|
internal static let usingDefaultBrowser = L10n.tr("Localizable", "Scene.Settings.Section.Preference.UsingDefaultBrowser")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
|
||||||
// }
|
|
||||||
//}
|
|
|
@ -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]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<MastodonStatusContent.ParseResult?, Never> {
|
|
||||||
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: "</p>", with: "</p>\r\n")
|
|
||||||
for (shortcode, url) in emojiDict {
|
|
||||||
let emojiNode = "<span class=\"emoji\" href=\"\(url.absoluteString)\">\(shortcode)</span>"
|
|
||||||
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..<entity.text.endIndex, in: text)
|
|
||||||
|
|
||||||
switch entity.type {
|
|
||||||
case .url:
|
|
||||||
guard let href = entity.href else { continue }
|
|
||||||
let text = String(entity.text)
|
|
||||||
activeEntities.append(ActiveEntity(range: range, type: .url(text, trimmed: entity.hrefEllipsis ?? text, url: href, userInfo: nil)))
|
|
||||||
case .hashtag:
|
|
||||||
var userInfo: [AnyHashable: Any] = [:]
|
|
||||||
entity.href.flatMap { href in
|
|
||||||
userInfo["href"] = href
|
|
||||||
}
|
|
||||||
let hashtag = String(entity.text).deletingPrefix("#")
|
|
||||||
activeEntities.append(ActiveEntity(range: range, type: .hashtag(hashtag, userInfo: userInfo)))
|
|
||||||
case .mention:
|
|
||||||
var userInfo: [AnyHashable: Any] = [:]
|
|
||||||
entity.href.flatMap { href in
|
|
||||||
userInfo["href"] = href
|
|
||||||
}
|
|
||||||
let mention = String(entity.text).deletingPrefix("@")
|
|
||||||
activeEntities.append(ActiveEntity(range: range, type: .mention(mention, userInfo: userInfo)))
|
|
||||||
case .emoji:
|
|
||||||
var userInfo: [AnyHashable: Any] = [:]
|
|
||||||
guard let href = entity.href else { continue }
|
|
||||||
userInfo["href"] = href
|
|
||||||
let emoji = String(entity.text)
|
|
||||||
activeEntities.append(ActiveEntity(range: range, type: .emoji(emoji, url: href, userInfo: userInfo)))
|
|
||||||
case .none:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var trimmed = text
|
|
||||||
for activeEntity in activeEntities {
|
|
||||||
MastodonStatusContent.trimEntity(status: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseResult(
|
|
||||||
document: document,
|
|
||||||
original: text,
|
|
||||||
trimmed: trimmed,
|
|
||||||
activeEntities: activeEntities
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func trimEntity(status: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) {
|
|
||||||
let text: String
|
|
||||||
let trimmed: String
|
|
||||||
switch activeEntity.type {
|
|
||||||
case .url(let _text, let _trimmed, _, _):
|
|
||||||
text = _text
|
|
||||||
trimmed = _trimmed
|
|
||||||
case .emoji(let _text, _, _):
|
|
||||||
text = _text
|
|
||||||
trimmed = " "
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let index = activeEntities.firstIndex(where: { $0.range == activeEntity.range }) else { return }
|
|
||||||
guard let range = Range(activeEntity.range, in: status) else { return }
|
|
||||||
status.replaceSubrange(range, with: trimmed)
|
|
||||||
|
|
||||||
let offset = trimmed.count - text.count
|
|
||||||
activeEntity.range.length += offset
|
|
||||||
|
|
||||||
let moveActiveEntities = Array(activeEntities[index...].dropFirst())
|
|
||||||
for moveActiveEntity in moveActiveEntities {
|
|
||||||
moveActiveEntity.range.location += offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
// ref: https://www.hackingwithswift.com/example-code/strings/how-to-remove-a-prefix-from-a-string
|
|
||||||
func deletingPrefix(_ prefix: String) -> 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<String.Index> {
|
|
||||||
return text.startIndex..<text.endIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
let tagName: String?
|
|
||||||
let attributes: [String : String]
|
|
||||||
let href: String?
|
|
||||||
let hrefEllipsis: String?
|
|
||||||
|
|
||||||
let children: [Node]
|
|
||||||
|
|
||||||
init(
|
|
||||||
level: Int,
|
|
||||||
text: Substring,
|
|
||||||
tagName: String?,
|
|
||||||
attributes: [String : String],
|
|
||||||
href: String?,
|
|
||||||
hrefEllipsis: String?,
|
|
||||||
children: [Node]
|
|
||||||
) {
|
|
||||||
let _classNames: Set<String> = {
|
|
||||||
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: "<br>|<br />", 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..<endIndex])
|
|
||||||
|
|
||||||
let href = _element["href"]
|
|
||||||
let hrefEllipsis = href.flatMap { _ in _element.firstChild(css: ".ellipsis")?.stringValue }
|
|
||||||
|
|
||||||
let level = parentLevel + 1
|
|
||||||
let node = Node(
|
|
||||||
level: level,
|
|
||||||
text: text,
|
|
||||||
tagName: _element.tag,
|
|
||||||
attributes: _element.attributes,
|
|
||||||
href: href,
|
|
||||||
hrefEllipsis: hrefEllipsis,
|
|
||||||
children: Node.parse(element: _element, parentText: text, parentLevel: level + 1)
|
|
||||||
)
|
|
||||||
children.append(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
|
|
||||||
static func collect(
|
|
||||||
node: Node,
|
|
||||||
where predicate: (Node) -> 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -26,4 +26,13 @@ extension UserDefaults {
|
||||||
set { self[#function] = newValue }
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,10 @@
|
||||||
#if ASDK
|
#if ASDK
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import ActiveLabel
|
|
||||||
|
|
||||||
// MARK: - StatusViewDelegate
|
// MARK: - StatusViewDelegate
|
||||||
extension StatusNodeDelegate where Self: StatusProvider {
|
extension StatusNodeDelegate where Self: StatusProvider {
|
||||||
func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType) {
|
|
||||||
StatusProviderFacade.responseToStatusActiveLabelAction(provider: self, node: node, didSelectActiveEntityType: type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -11,9 +11,8 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import ActiveLabel
|
|
||||||
import Meta
|
import Meta
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
// MARK: - StatusViewDelegate
|
// MARK: - StatusViewDelegate
|
||||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
|
@ -25,10 +24,6 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, avatarImageViewDidPressed imageView: UIImageView) {
|
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, avatarImageViewDidPressed imageView: UIImageView) {
|
||||||
StatusProviderFacade.coordinateToStatusAuthorProfileScene(for: .primary, provider: self, cell: cell)
|
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) {
|
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) {
|
||||||
StatusProviderFacade.responseToStatusMetaTextAction(provider: self, cell: cell, metaText: metaText, didSelectMeta: meta)
|
StatusProviderFacade.responseToStatusMetaTextAction(provider: self, cell: cell, metaText: metaText, didSelectMeta: meta)
|
||||||
|
|
|
@ -11,9 +11,8 @@ import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import ActiveLabel
|
|
||||||
import Meta
|
import Meta
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
#if ASDK
|
#if ASDK
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
@ -125,35 +124,6 @@ extension StatusProviderFacade {
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
static func responseToStatusMetaTextAction(provider: StatusProvider, cell: UITableViewCell, metaText: MetaText, didSelectMeta meta: Meta) {
|
||||||
switch meta {
|
switch meta {
|
||||||
|
@ -185,31 +155,6 @@ extension StatusProviderFacade {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ASDK
|
#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) {
|
private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, node: ASCellNode, mention: String) {
|
||||||
guard let status = provider.status(node: node, indexPath: nil) else { return }
|
guard let status = provider.status(node: node, indexPath: nil) else { return }
|
||||||
coordinateToStatusMentionProfileScene(for: target, provider: provider, status: status, mention: mention, href: nil)
|
coordinateToStatusMentionProfileScene(for: target, provider: provider, status: status, mention: mention, href: nil)
|
||||||
|
|
|
@ -321,8 +321,6 @@ any server.";
|
||||||
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
||||||
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
||||||
"Scene.Settings.Section.Appearance.Title" = "Appearance";
|
"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.AccountSettings" = "Account Settings";
|
||||||
"Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy";
|
"Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy";
|
||||||
"Scene.Settings.Section.BoringZone.Terms" = "Terms of Service";
|
"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.Follower" = "a follower";
|
||||||
"Scene.Settings.Section.Notifications.Trigger.Noone" = "no one";
|
"Scene.Settings.Section.Notifications.Trigger.Noone" = "no one";
|
||||||
"Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when";
|
"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.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.Preference.UsingDefaultBrowser" = "Use default browser to open links";
|
||||||
"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache";
|
"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache";
|
||||||
"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out";
|
"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out";
|
||||||
|
|
|
@ -321,8 +321,6 @@ any server.";
|
||||||
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
||||||
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
||||||
"Scene.Settings.Section.Appearance.Title" = "Appearance";
|
"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.AccountSettings" = "Account Settings";
|
||||||
"Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy";
|
"Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy";
|
||||||
"Scene.Settings.Section.BoringZone.Terms" = "Terms of Service";
|
"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.Follower" = "a follower";
|
||||||
"Scene.Settings.Section.Notifications.Trigger.Noone" = "no one";
|
"Scene.Settings.Section.Notifications.Trigger.Noone" = "no one";
|
||||||
"Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when";
|
"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.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.Preference.UsingDefaultBrowser" = "Use default browser to open links";
|
||||||
"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache";
|
"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache";
|
||||||
"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out";
|
"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out";
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
import UITextView_Placeholder
|
import UITextView_Placeholder
|
||||||
|
|
||||||
final class ComposeStatusContentTableViewCell: UITableViewCell {
|
final class ComposeStatusContentTableViewCell: UITableViewCell {
|
||||||
|
@ -40,20 +40,19 @@ final class ComposeStatusContentTableViewCell: UITableViewCell {
|
||||||
attributes: attributes
|
attributes: attributes
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
let paragraphStyle: NSMutableParagraphStyle = {
|
metaText.paragraphStyle = {
|
||||||
let style = NSMutableParagraphStyle()
|
let style = NSMutableParagraphStyle()
|
||||||
style.lineSpacing = 5
|
style.lineSpacing = 5
|
||||||
|
style.paragraphSpacing = 8
|
||||||
return style
|
return style
|
||||||
}()
|
}()
|
||||||
metaText.textAttributes = [
|
metaText.textAttributes = [
|
||||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
||||||
.foregroundColor: Asset.Colors.Label.primary.color,
|
.foregroundColor: Asset.Colors.Label.primary.color,
|
||||||
.paragraphStyle: paragraphStyle,
|
|
||||||
]
|
]
|
||||||
metaText.linkAttributes = [
|
metaText.linkAttributes = [
|
||||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
||||||
.foregroundColor: Asset.Colors.brandBlue.color,
|
.foregroundColor: Asset.Colors.brandBlue.color,
|
||||||
.paragraphStyle: paragraphStyle,
|
|
||||||
]
|
]
|
||||||
return metaText
|
return metaText
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import FLAnimatedImage
|
import SDWebImage
|
||||||
|
|
||||||
final class CustomEmojiPickerItemCollectionViewCell: UICollectionViewCell {
|
final class CustomEmojiPickerItemCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
static let itemSize = CGSize(width: 44, height: 44)
|
static let itemSize = CGSize(width: 44, height: 44)
|
||||||
|
|
||||||
let emojiImageView: FLAnimatedImageView = {
|
let emojiImageView: SDAnimatedImageView = {
|
||||||
let imageView = FLAnimatedImageView()
|
let imageView = SDAnimatedImageView()
|
||||||
imageView.contentMode = .scaleAspectFit
|
imageView.contentMode = .scaleAspectFit
|
||||||
imageView.layer.masksToBounds = true
|
imageView.layer.masksToBounds = true
|
||||||
return imageView
|
return imageView
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
import Meta
|
import Meta
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
|
|
@ -9,10 +9,9 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import TwitterTextEditor
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
extension ComposeViewModel {
|
extension ComposeViewModel {
|
||||||
|
|
||||||
|
@ -193,8 +192,15 @@ extension ComposeViewModel: UITableViewDataSource {
|
||||||
|
|
||||||
// set avatar
|
// set avatar
|
||||||
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
||||||
// set name username
|
// set name, username
|
||||||
cell.statusView.nameLabel.configure(content: status.author.displayNameWithFallback, emojiDict: status.author.emojiDict)
|
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
|
cell.statusView.usernameLabel.text = "@" + status.author.acct
|
||||||
// set text
|
// set text
|
||||||
let content = MastodonContent(content: status.content, emojis: status.emojiMeta)
|
let content = MastodonContent(content: status.content, emojis: status.emojiMeta)
|
||||||
|
@ -226,13 +232,14 @@ extension ComposeViewModel: UITableViewDataSource {
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||||
return L10n.Scene.Compose.replyingToUser(name)
|
return L10n.Scene.Compose.replyingToUser(name)
|
||||||
}()
|
}()
|
||||||
MastodonStatusContent.parseResult(content: headerText, emojiDict: replyTo.author.emojiDict)
|
do {
|
||||||
.receive(on: DispatchQueue.main)
|
let mastodonContent = MastodonContent(content: headerText, emojis: replyTo.author.emojiMeta)
|
||||||
.sink { [weak cell] parseResult in
|
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||||
guard let cell = cell else { return }
|
cell.statusView.headerInfoLabel.configure(content: metaContent)
|
||||||
cell.statusView.headerInfoLabel.configure(contentParseResult: parseResult)
|
} catch {
|
||||||
}
|
let metaContent = PlaintextMetaContent(string: headerText)
|
||||||
.store(in: &cell.disposeBag)
|
cell.statusView.headerInfoLabel.configure(content: metaContent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// configure author
|
// configure author
|
||||||
ComposeStatusSection.configureStatusContent(cell: cell, attribute: composeStatusAttribute)
|
ComposeStatusSection.configureStatusContent(cell: cell, attribute: composeStatusAttribute)
|
||||||
|
|
|
@ -181,7 +181,7 @@ final class ComposeViewModel: NSObject {
|
||||||
}
|
}
|
||||||
return displayName
|
return displayName
|
||||||
}()
|
}()
|
||||||
self.composeStatusAttribute.emojiDict.value = mastodonUser?.emojiDict ?? [:]
|
self.composeStatusAttribute.emojiMeta.value = mastodonUser?.emojiMeta ?? [:]
|
||||||
self.composeStatusAttribute.username.value = username
|
self.composeStatusAttribute.username.value = username
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
final class CustomEmojiPickerInputViewModel {
|
final class CustomEmojiPickerInputViewModel {
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,8 @@
|
||||||
|
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import ActiveLabel
|
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
final class ReplicaStatusView: UIView {
|
final class ReplicaStatusView: UIView {
|
||||||
|
|
||||||
|
@ -52,12 +51,7 @@ final class ReplicaStatusView: UIView {
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let headerInfoLabel: ActiveLabel = {
|
let headerInfoLabel = MetaLabel(style: .statusHeader)
|
||||||
let label = ActiveLabel(style: .statusHeader)
|
|
||||||
label.text = "Bob reblogged"
|
|
||||||
label.layer.masksToBounds = false
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
let avatarView: UIView = {
|
let avatarView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
@ -68,10 +62,7 @@ final class ReplicaStatusView: UIView {
|
||||||
}()
|
}()
|
||||||
let avatarImageView = FLAnimatedImageView()
|
let avatarImageView = FLAnimatedImageView()
|
||||||
|
|
||||||
let nameLabel: ActiveLabel = {
|
let nameLabel = MetaLabel(style: .statusName)
|
||||||
let label = ActiveLabel(style: .statusName)
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
let nameTrialingDotLabel: UILabel = {
|
let nameTrialingDotLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
|
|
@ -56,7 +56,7 @@ extension HashtagTimelineViewController {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
title = "#\(viewModel.hashtag)"
|
title = "#\(viewModel.hashtag)"
|
||||||
titleView.update(title: viewModel.hashtag, subtitle: nil, emojiDict: [:])
|
titleView.update(title: viewModel.hashtag, subtitle: nil)
|
||||||
navigationItem.titleView = titleView
|
navigationItem.titleView = titleView
|
||||||
|
|
||||||
view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor
|
view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor
|
||||||
|
@ -150,7 +150,7 @@ extension HashtagTimelineViewController {
|
||||||
private func updatePromptTitle() {
|
private func updatePromptTitle() {
|
||||||
var subtitle: String?
|
var subtitle: String?
|
||||||
defer {
|
defer {
|
||||||
titleView.update(title: "#" + viewModel.hashtag, subtitle: subtitle, emojiDict: [:])
|
titleView.update(title: "#" + viewModel.hashtag, subtitle: subtitle)
|
||||||
}
|
}
|
||||||
guard let histories = viewModel.hashtagEntity.value?.history else {
|
guard let histories = viewModel.hashtagEntity.value?.history else {
|
||||||
return
|
return
|
||||||
|
@ -209,7 +209,7 @@ extension HashtagTimelineViewController: LoadMoreConfigurableTableViewContainer
|
||||||
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
||||||
typealias LoadingState = HashtagTimelineViewModel.LoadOldestState.Loading
|
typealias LoadingState = HashtagTimelineViewModel.LoadOldestState.Loading
|
||||||
var loadMoreConfigurableTableView: UITableView { return tableView }
|
var loadMoreConfigurableTableView: UITableView { return tableView }
|
||||||
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadoldestStateMachine }
|
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadOldestStateMachine }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
|
|
|
@ -84,7 +84,7 @@ extension HashtagTimelineViewModel {
|
||||||
newSnapshot.appendItems(statusItemList, toSection: .main)
|
newSnapshot.appendItems(statusItemList, toSection: .main)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(self.loadoldestStateMachine.currentState is LoadOldestState.NoMore) {
|
if !(self.loadOldestStateMachine.currentState is LoadOldestState.NoMore) {
|
||||||
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ final class HashtagTimelineViewModel: NSObject {
|
||||||
}()
|
}()
|
||||||
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
||||||
// bottom loader
|
// bottom loader
|
||||||
private(set) lazy var loadoldestStateMachine: GKStateMachine = {
|
private(set) lazy var loadOldestStateMachine: GKStateMachine = {
|
||||||
// exclude timeline middle fetcher state
|
// exclude timeline middle fetcher state
|
||||||
let stateMachine = GKStateMachine(states: [
|
let stateMachine = GKStateMachine(states: [
|
||||||
LoadOldestState.Initial(viewModel: self),
|
LoadOldestState.Initial(viewModel: self),
|
||||||
|
|
|
@ -109,6 +109,12 @@ extension AsyncHomeTimelineViewController: StatusProvider {
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ extension AsyncHomeTimelineViewController {
|
||||||
node.allowsSelection = true
|
node.allowsSelection = true
|
||||||
|
|
||||||
title = L10n.Scene.HomeTimeline.title
|
title = L10n.Scene.HomeTimeline.title
|
||||||
view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color
|
view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor
|
||||||
navigationItem.leftBarButtonItem = settingBarButtonItem
|
navigationItem.leftBarButtonItem = settingBarButtonItem
|
||||||
navigationItem.titleView = titleView
|
navigationItem.titleView = titleView
|
||||||
titleView.delegate = self
|
titleView.delegate = self
|
||||||
|
@ -341,7 +341,7 @@ extension AsyncHomeTimelineViewController {
|
||||||
// typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
// typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
||||||
// typealias LoadingState = HomeTimelineViewModel.LoadOldestState.Loading
|
// typealias LoadingState = HomeTimelineViewModel.LoadOldestState.Loading
|
||||||
// var loadMoreConfigurableTableView: UITableView { return tableView }
|
// var loadMoreConfigurableTableView: UITableView { return tableView }
|
||||||
// var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadoldestStateMachine }
|
// var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadOldestStateMachine }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
|
@ -556,7 +556,7 @@ extension AsyncHomeTimelineViewController: ASTableDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) {
|
func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) {
|
||||||
viewModel.loadoldestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self)
|
viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self)
|
||||||
context.completeBatchFetching(true)
|
context.completeBatchFetching(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ extension AsyncHomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
||||||
|
|
||||||
let endSnapshot = CACurrentMediaTime()
|
let endSnapshot = CACurrentMediaTime()
|
||||||
|
|
||||||
if shouldAddBottomLoader, !(self.loadoldestStateMachine.currentState is LoadOldestState.NoMore) {
|
if shouldAddBottomLoader, !(self.loadLatestStateMachine.currentState is LoadOldestState.NoMore) {
|
||||||
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import CoreDataStack
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import DateToolsSwift
|
import DateToolsSwift
|
||||||
import ActiveLabel
|
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
|
||||||
final class AsyncHomeTimelineViewModel: NSObject {
|
final class AsyncHomeTimelineViewModel: NSObject {
|
||||||
|
@ -59,7 +58,7 @@ final class AsyncHomeTimelineViewModel: NSObject {
|
||||||
}()
|
}()
|
||||||
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
||||||
// bottom loader
|
// bottom loader
|
||||||
private(set) lazy var loadoldestStateMachine: GKStateMachine = {
|
private(set) lazy var loadOldestStateMachine: GKStateMachine = {
|
||||||
// exclude timeline middle fetcher state
|
// exclude timeline middle fetcher state
|
||||||
let stateMachine = GKStateMachine(states: [
|
let stateMachine = GKStateMachine(states: [
|
||||||
LoadOldestState.Initial(viewModel: self),
|
LoadOldestState.Initial(viewModel: self),
|
||||||
|
|
|
@ -388,7 +388,7 @@ extension HomeTimelineViewController: LoadMoreConfigurableTableViewContainer {
|
||||||
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
||||||
typealias LoadingState = HomeTimelineViewModel.LoadOldestState.Loading
|
typealias LoadingState = HomeTimelineViewModel.LoadOldestState.Loading
|
||||||
var loadMoreConfigurableTableView: UITableView { return tableView }
|
var loadMoreConfigurableTableView: UITableView { return tableView }
|
||||||
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadoldestStateMachine }
|
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.loadLatestStateMachine }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
|
|
|
@ -115,7 +115,7 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
||||||
let endSnapshot = CACurrentMediaTime()
|
let endSnapshot = CACurrentMediaTime()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if shouldAddBottomLoader, !(self.loadoldestStateMachine.currentState is LoadOldestState.NoMore) {
|
if shouldAddBottomLoader, !(self.loadLatestStateMachine.currentState is LoadOldestState.NoMore) {
|
||||||
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import CoreDataStack
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import DateToolsSwift
|
import DateToolsSwift
|
||||||
import ActiveLabel
|
|
||||||
|
|
||||||
final class HomeTimelineViewModel: NSObject {
|
final class HomeTimelineViewModel: NSObject {
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ final class HomeTimelineViewModel: NSObject {
|
||||||
}()
|
}()
|
||||||
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
||||||
// bottom loader
|
// bottom loader
|
||||||
private(set) lazy var loadoldestStateMachine: GKStateMachine = {
|
private(set) lazy var loadOldestStateMachine: GKStateMachine = {
|
||||||
// exclude timeline middle fetcher state
|
// exclude timeline middle fetcher state
|
||||||
let stateMachine = GKStateMachine(states: [
|
let stateMachine = GKStateMachine(states: [
|
||||||
LoadOldestState.Initial(viewModel: self),
|
LoadOldestState.Initial(viewModel: self),
|
||||||
|
|
|
@ -12,9 +12,8 @@ import GameplayKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import OSLog
|
import OSLog
|
||||||
import UIKit
|
import UIKit
|
||||||
import ActiveLabel
|
|
||||||
import Meta
|
import Meta
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
final class NotificationViewController: UIViewController, NeedsDependency {
|
final class NotificationViewController: UIViewController, NeedsDependency {
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
@ -272,7 +271,7 @@ extension NotificationViewController {
|
||||||
switch item {
|
switch item {
|
||||||
case .bottomLoader:
|
case .bottomLoader:
|
||||||
if !tableView.isDragging, !tableView.isDecelerating {
|
if !tableView.isDragging, !tableView.isDecelerating {
|
||||||
viewModel.loadoldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self)
|
viewModel.loadOldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
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 diffableDataSource = viewModel.diffableDataSource else { return }
|
||||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) 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) {
|
func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) {
|
||||||
viewModel.acceptFollowRequest(notification: notification)
|
viewModel.acceptFollowRequest(notification: notification)
|
||||||
}
|
}
|
||||||
|
@ -380,7 +378,7 @@ extension NotificationViewController: LoadMoreConfigurableTableViewContainer {
|
||||||
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
||||||
typealias LoadingState = NotificationViewModel.LoadOldestState.Loading
|
typealias LoadingState = NotificationViewModel.LoadOldestState.Loading
|
||||||
var loadMoreConfigurableTableView: UITableView { tableView }
|
var loadMoreConfigurableTableView: UITableView { tableView }
|
||||||
var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadoldestStateMachine }
|
var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadOldestStateMachine }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationViewController {
|
extension NotificationViewController {
|
||||||
|
|
|
@ -54,7 +54,7 @@ final class NotificationViewModel: NSObject {
|
||||||
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
||||||
|
|
||||||
// bottom loader
|
// bottom loader
|
||||||
private(set) lazy var loadoldestStateMachine: GKStateMachine = {
|
private(set) lazy var loadOldestStateMachine: GKStateMachine = {
|
||||||
// exclude timeline middle fetcher state
|
// exclude timeline middle fetcher state
|
||||||
let stateMachine = GKStateMachine(states: [
|
let stateMachine = GKStateMachine(states: [
|
||||||
LoadOldestState.Initial(viewModel: self),
|
LoadOldestState.Initial(viewModel: self),
|
||||||
|
|
|
@ -10,8 +10,7 @@ import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import UIKit
|
import UIKit
|
||||||
import ActiveLabel
|
import MetaTextKit
|
||||||
import MetaTextView
|
|
||||||
import Meta
|
import Meta
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ protocol NotificationTableViewCellDelegate: AnyObject {
|
||||||
func parent() -> UIViewController
|
func parent() -> UIViewController
|
||||||
|
|
||||||
func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, avatarImageViewDidPressed imageView: UIImageView)
|
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, revealContentWarningButtonDidPressed button: UIButton)
|
||||||
func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
||||||
|
@ -58,13 +57,7 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let nameLabel: ActiveLabel = {
|
let nameLabel = MetaLabel(style: .notificationName)
|
||||||
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 buttonStackView = UIStackView()
|
let buttonStackView = UIStackView()
|
||||||
|
|
||||||
|
@ -318,10 +311,6 @@ extension NotificationStatusTableViewCell: StatusViewDelegate {
|
||||||
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) {
|
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
func statusView(_ statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) {
|
func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) {
|
||||||
delegate?.notificationStatusTableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta)
|
delegate?.notificationStatusTableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta)
|
||||||
|
|
|
@ -57,7 +57,7 @@ extension FavoriteViewController {
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
navigationItem.titleView = titleView
|
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
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(tableView)
|
view.addSubview(tableView)
|
||||||
|
|
|
@ -9,16 +9,15 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
import ActiveLabel
|
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import CropViewController
|
import CropViewController
|
||||||
import TwitterTextEditor
|
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
|
import MetaTextKit
|
||||||
|
|
||||||
protocol ProfileHeaderViewControllerDelegate: AnyObject {
|
protocol ProfileHeaderViewControllerDelegate: AnyObject {
|
||||||
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView)
|
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView)
|
||||||
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, pageSegmentedControlValueChanged segmentedControl: UISegmentedControl, selectedSegmentIndex index: Int)
|
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 {
|
final class ProfileHeaderViewController: UIViewController {
|
||||||
|
@ -35,6 +34,7 @@ final class ProfileHeaderViewController: UIViewController {
|
||||||
let titleView: DoubleTitleLabelNavigationBarTitleView = {
|
let titleView: DoubleTitleLabelNavigationBarTitleView = {
|
||||||
let titleView = DoubleTitleLabelNavigationBarTitleView()
|
let titleView = DoubleTitleLabelNavigationBarTitleView()
|
||||||
titleView.titleLabel.textColor = .white
|
titleView.titleLabel.textColor = .white
|
||||||
|
titleView.titleLabel.textAttributes[.foregroundColor] = UIColor.white
|
||||||
titleView.titleLabel.alpha = 0
|
titleView.titleLabel.alpha = 0
|
||||||
titleView.subtitleLabel.textColor = .white
|
titleView.subtitleLabel.textColor = .white
|
||||||
titleView.subtitleLabel.alpha = 0
|
titleView.subtitleLabel.alpha = 0
|
||||||
|
@ -179,19 +179,14 @@ extension ProfileHeaderViewController {
|
||||||
viewModel.isEditing,
|
viewModel.isEditing,
|
||||||
viewModel.displayProfileInfo.name.removeDuplicates(),
|
viewModel.displayProfileInfo.name.removeDuplicates(),
|
||||||
viewModel.editProfileInfo.name.removeDuplicates(),
|
viewModel.editProfileInfo.name.removeDuplicates(),
|
||||||
viewModel.emojiDict
|
viewModel.emojiMeta
|
||||||
)
|
)
|
||||||
.receive(on: DispatchQueue.main)
|
.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 }
|
guard let self = self else { return }
|
||||||
do {
|
do {
|
||||||
var emojis = MastodonContent.Emojis()
|
let mastodonContent = MastodonContent(content: name ?? " ", emojis: emojiMeta)
|
||||||
for (key, value) in emojiDict {
|
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||||
emojis[key] = value.absoluteString
|
|
||||||
}
|
|
||||||
let metaContent = try MastodonMetaContent.convert(
|
|
||||||
document: MastodonContent(content: name ?? " ", emojis: emojis)
|
|
||||||
)
|
|
||||||
self.profileHeaderView.nameMetaText.configure(content: metaContent)
|
self.profileHeaderView.nameMetaText.configure(content: metaContent)
|
||||||
} catch {
|
} catch {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
|
@ -200,25 +195,37 @@ extension ProfileHeaderViewController {
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
Publishers.CombineLatest3(
|
Publishers.CombineLatest4(
|
||||||
viewModel.isEditing.eraseToAnyPublisher(),
|
viewModel.isEditing.removeDuplicates(),
|
||||||
viewModel.displayProfileInfo.note.removeDuplicates().eraseToAnyPublisher(),
|
viewModel.displayProfileInfo.note.removeDuplicates(),
|
||||||
viewModel.editProfileInfo.note.removeDuplicates().eraseToAnyPublisher()
|
viewModel.editProfileInfo.note.removeDuplicates(),
|
||||||
|
viewModel.emojiMeta.removeDuplicates()
|
||||||
)
|
)
|
||||||
.receive(on: DispatchQueue.main)
|
.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 }
|
guard let self = self else { return }
|
||||||
self.profileHeaderView.bioActiveLabel.configure(note: note ?? "", emojiDict: [:]) // FIXME: custom emoji
|
|
||||||
|
|
||||||
// prevent duplicate set
|
self.profileHeaderView.bioMetaText.textView.isEditable = isEditing
|
||||||
let editingNote = editingNote ?? ""
|
|
||||||
if self.profileHeaderView.bioTextEditorView.text != editingNote {
|
if isEditing {
|
||||||
self.profileHeaderView.bioTextEditorView.text = editingNote
|
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)
|
.store(in: &disposeBag)
|
||||||
|
profileHeaderView.bioMetaText.delegate = self
|
||||||
profileHeaderView.bioTextEditorView.changeObserver = self
|
|
||||||
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: profileHeaderView.nameTextField)
|
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: profileHeaderView.nameTextField)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] notification in
|
.sink { [weak self] notification in
|
||||||
|
@ -450,13 +457,16 @@ extension ProfileHeaderViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - TextEditorViewChangeObserver
|
// MARK: - MetaTextDelegate
|
||||||
extension ProfileHeaderViewController: TextEditorViewChangeObserver {
|
extension ProfileHeaderViewController: MetaTextDelegate {
|
||||||
func textEditorView(_ textEditorView: TextEditorView, didChangeWithChangeResult changeResult: TextEditorViewChangeResult) {
|
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, textEditorView.text)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: text: %s", ((#file as NSString).lastPathComponent), #line, #function, metaText.backedString)
|
||||||
guard changeResult.isTextChanged else { return }
|
assert(metaText.textView === profileHeaderView.bioMetaText.textView)
|
||||||
assert(textEditorView === profileHeaderView.bioTextEditorView)
|
if metaText.textView === profileHeaderView.bioMetaText.textView {
|
||||||
viewModel.editProfileInfo.note.value = textEditorView.text
|
viewModel.editProfileInfo.note.value = metaText.backedString
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,6 +543,7 @@ extension ProfileHeaderViewController: UICollectionViewDelegate {
|
||||||
|
|
||||||
// MARK: - ProfileFieldCollectionViewCellDelegate
|
// MARK: - ProfileFieldCollectionViewCellDelegate
|
||||||
extension ProfileHeaderViewController: ProfileFieldCollectionViewCellDelegate {
|
extension ProfileHeaderViewController: ProfileFieldCollectionViewCellDelegate {
|
||||||
|
|
||||||
// should be remove style edit button
|
// should be remove style edit button
|
||||||
func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, editButtonDidPressed button: UIButton) {
|
func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, editButtonDidPressed button: UIButton) {
|
||||||
guard let diffableDataSource = viewModel.fieldDiffableDataSource else { return }
|
guard let diffableDataSource = viewModel.fieldDiffableDataSource else { return }
|
||||||
|
@ -541,8 +552,8 @@ extension ProfileHeaderViewController: ProfileFieldCollectionViewCellDelegate {
|
||||||
viewModel.removeFieldItem(item: item)
|
viewModel.removeFieldItem(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) {
|
func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, metaLebel: MetaLabel, didSelectMeta meta: Meta) {
|
||||||
delegate?.profileHeaderViewController(self, profileFieldCollectionViewCell: cell, activeLabel: activeLabel, didSelectActiveEntity: entity)
|
delegate?.profileHeaderViewController(self, profileFieldCollectionViewCell: cell, metaLabel: metaLebel, didSelectMeta: meta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Kanna
|
import Kanna
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
final class ProfileHeaderViewModel {
|
final class ProfileHeaderViewModel {
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ final class ProfileHeaderViewModel {
|
||||||
let needsSetupBottomShadow = CurrentValueSubject<Bool, Never>(true)
|
let needsSetupBottomShadow = CurrentValueSubject<Bool, Never>(true)
|
||||||
let needsFiledCollectionViewHidden = CurrentValueSubject<Bool, Never>(false)
|
let needsFiledCollectionViewHidden = CurrentValueSubject<Bool, Never>(false)
|
||||||
let isTitleViewContentOffsetSet = CurrentValueSubject<Bool, Never>(false)
|
let isTitleViewContentOffsetSet = CurrentValueSubject<Bool, Never>(false)
|
||||||
let emojiDict = CurrentValueSubject<MastodonStatusContent.EmojiDict, Never>([:])
|
let emojiMeta = CurrentValueSubject<MastodonContent.Emojis, Never>([:])
|
||||||
let accountForEdit = CurrentValueSubject<Mastodon.Entity.Account?, Never>(nil)
|
let accountForEdit = CurrentValueSubject<Mastodon.Entity.Account?, Never>(nil)
|
||||||
|
|
||||||
// output
|
// output
|
||||||
|
@ -58,10 +59,10 @@ final class ProfileHeaderViewModel {
|
||||||
isEditing.removeDuplicates(),
|
isEditing.removeDuplicates(),
|
||||||
displayProfileInfo.fields.removeDuplicates(),
|
displayProfileInfo.fields.removeDuplicates(),
|
||||||
editProfileInfo.fields.removeDuplicates(),
|
editProfileInfo.fields.removeDuplicates(),
|
||||||
emojiDict.removeDuplicates()
|
emojiMeta.removeDuplicates()
|
||||||
)
|
)
|
||||||
.receive(on: RunLoop.main)
|
.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 self = self else { return }
|
||||||
guard let diffableDataSource = self.fieldDiffableDataSource else { return }
|
guard let diffableDataSource = self.fieldDiffableDataSource else { return }
|
||||||
|
|
||||||
|
@ -87,7 +88,7 @@ final class ProfileHeaderViewModel {
|
||||||
|
|
||||||
let attribute = oldFieldAttributeDict[field.id] ?? ProfileFieldItem.FieldItemAttribute()
|
let attribute = oldFieldAttributeDict[field.id] ?? ProfileFieldItem.FieldItemAttribute()
|
||||||
attribute.isEditing = isEditing
|
attribute.isEditing = isEditing
|
||||||
attribute.emojiDict.value = emojiDict
|
attribute.emojiMeta.value = emojiMeta
|
||||||
attribute.isLast = false
|
attribute.isLast = false
|
||||||
return ProfileFieldItem.field(field: field, attribute: attribute)
|
return ProfileFieldItem.field(field: field, attribute: attribute)
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,12 +95,12 @@ extension ProfileFieldAddEntryCollectionViewCell {
|
||||||
bottomSeparatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self)).priority(.defaultHigh),
|
bottomSeparatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self)).priority(.defaultHigh),
|
||||||
])
|
])
|
||||||
|
|
||||||
fieldView.titleActiveLabel.isHidden = false
|
fieldView.titleMetaLabel.isHidden = false
|
||||||
fieldView.titleActiveLabel.configure(field: L10n.Scene.Profile.Fields.addRow, emojiDict: [:])
|
fieldView.titleMetaLabel.configure(content: PlaintextMetaContent(string: L10n.Scene.Profile.Fields.addRow))
|
||||||
fieldView.titleTextField.isHidden = true
|
fieldView.titleTextField.isHidden = true
|
||||||
|
|
||||||
fieldView.valueActiveLabel.isHidden = false
|
fieldView.valueMetaLabel.isHidden = false
|
||||||
fieldView.valueActiveLabel.configure(field: " ", emojiDict: [:])
|
fieldView.valueMetaLabel.configure(content: PlaintextMetaContent(string: " "))
|
||||||
fieldView.valueTextField.isHidden = true
|
fieldView.valueTextField.isHidden = true
|
||||||
|
|
||||||
addGestureRecognizer(singleTagGestureRecognizer)
|
addGestureRecognizer(singleTagGestureRecognizer)
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import ActiveLabel
|
import MetaTextKit
|
||||||
|
|
||||||
protocol ProfileFieldCollectionViewCellDelegate: AnyObject {
|
protocol ProfileFieldCollectionViewCellDelegate: AnyObject {
|
||||||
func profileFieldCollectionViewCell(_ cell: ProfileFieldCollectionViewCell, editButtonDidPressed button: UIButton)
|
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 {
|
final class ProfileFieldCollectionViewCell: UICollectionViewCell {
|
||||||
|
@ -107,7 +107,7 @@ extension ProfileFieldCollectionViewCell {
|
||||||
|
|
||||||
editButton.addTarget(self, action: #selector(ProfileFieldCollectionViewCell.editButtonDidPressed(_:)), for: .touchUpInside)
|
editButton.addTarget(self, action: #selector(ProfileFieldCollectionViewCell.editButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
fieldView.valueActiveLabel.delegate = self
|
fieldView.valueMetaLabel.linkDelegate = self
|
||||||
|
|
||||||
resetSeparatorLineLayout()
|
resetSeparatorLineLayout()
|
||||||
}
|
}
|
||||||
|
@ -153,13 +153,11 @@ extension ProfileFieldCollectionViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - MetaLabelDelegate
|
||||||
|
extension ProfileFieldCollectionViewCell: MetaLabelDelegate {
|
||||||
// MARK: - ActiveLabelDelegate
|
func metaLabel(_ metaLabel: MetaLabel, didSelectMeta meta: Meta) {
|
||||||
extension ProfileFieldCollectionViewCell: ActiveLabelDelegate {
|
|
||||||
func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import ActiveLabel
|
import MetaTextKit
|
||||||
|
|
||||||
final class ProfileFieldView: UIView {
|
final class ProfileFieldView: UIView {
|
||||||
|
|
||||||
|
@ -18,11 +18,7 @@ final class ProfileFieldView: UIView {
|
||||||
let value = PassthroughSubject<String, Never>()
|
let value = PassthroughSubject<String, Never>()
|
||||||
|
|
||||||
// for custom emoji display
|
// for custom emoji display
|
||||||
let titleActiveLabel: ActiveLabel = {
|
let titleMetaLabel = MetaLabel(style: .profileFieldName)
|
||||||
let label = ActiveLabel(style: .profileFieldName)
|
|
||||||
label.configure(content: "title", emojiDict: [:])
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
// for editing
|
// for editing
|
||||||
let titleTextField: UITextField = {
|
let titleTextField: UITextField = {
|
||||||
|
@ -34,12 +30,7 @@ final class ProfileFieldView: UIView {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// for custom emoji display
|
// for custom emoji display
|
||||||
let valueActiveLabel: ActiveLabel = {
|
let valueMetaLabel = MetaLabel(style: .profileFieldValue)
|
||||||
let label = ActiveLabel(style: .profileFieldValue)
|
|
||||||
label.configure(content: "value", emojiDict: [:])
|
|
||||||
label.textAlignment = .right
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
// for editing
|
// for editing
|
||||||
let valueTextField: UITextField = {
|
let valueTextField: UITextField = {
|
||||||
|
@ -81,10 +72,10 @@ extension ProfileFieldView {
|
||||||
containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
])
|
||||||
titleActiveLabel.translatesAutoresizingMaskIntoConstraints = false
|
titleMetaLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
containerStackView.addArrangedSubview(titleActiveLabel)
|
containerStackView.addArrangedSubview(titleMetaLabel)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
titleActiveLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh),
|
titleMetaLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh),
|
||||||
])
|
])
|
||||||
titleTextField.setContentHuggingPriority(.defaultLow - 1, for: .horizontal)
|
titleTextField.setContentHuggingPriority(.defaultLow - 1, for: .horizontal)
|
||||||
titleTextField.translatesAutoresizingMaskIntoConstraints = false
|
titleTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -94,12 +85,12 @@ extension ProfileFieldView {
|
||||||
])
|
])
|
||||||
titleTextField.setContentHuggingPriority(.defaultLow - 1, for: .horizontal)
|
titleTextField.setContentHuggingPriority(.defaultLow - 1, for: .horizontal)
|
||||||
|
|
||||||
valueActiveLabel.translatesAutoresizingMaskIntoConstraints = false
|
valueMetaLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
containerStackView.addArrangedSubview(valueActiveLabel)
|
containerStackView.addArrangedSubview(valueMetaLabel)
|
||||||
NSLayoutConstraint.activate([
|
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
|
valueTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
containerStackView.addArrangedSubview(valueTextField)
|
containerStackView.addArrangedSubview(valueTextField)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -137,7 +128,8 @@ struct ProfileFieldView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
UIViewPreview(width: 375) {
|
UIViewPreview(width: 375) {
|
||||||
let filedView = ProfileFieldView()
|
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
|
return filedView
|
||||||
}
|
}
|
||||||
.previewLayout(.fixed(width: 375, height: 100))
|
.previewLayout(.fixed(width: 375, height: 100))
|
||||||
|
|
|
@ -8,16 +8,14 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import ActiveLabel
|
|
||||||
import TwitterTextEditor
|
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
protocol ProfileHeaderViewDelegate: AnyObject {
|
protocol ProfileHeaderViewDelegate: AnyObject {
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView)
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView)
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton)
|
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, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView)
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed followingDashboardMeterView: ProfileStatusDashboardMeterView)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed followingDashboardMeterView: ProfileStatusDashboardMeterView)
|
||||||
|
@ -166,28 +164,38 @@ final class ProfileHeaderView: UIView {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let bioContainerView = UIView()
|
let bioContainerView = UIView()
|
||||||
let bioContainerStackView = UIStackView()
|
|
||||||
let fieldContainerStackView = UIStackView()
|
let fieldContainerStackView = UIStackView()
|
||||||
|
|
||||||
let bioActiveLabelContainer: UIView = {
|
let bioMetaText: MetaText = {
|
||||||
// use to set margin for active label
|
let metaText = MetaText()
|
||||||
// the display/edit mode bio transition animation should without flicker with that
|
metaText.textView.backgroundColor = .clear
|
||||||
let view = UIView()
|
metaText.textView.isEditable = false
|
||||||
// note: comment out to see how it works
|
metaText.textView.isSelectable = true
|
||||||
view.layoutMargins = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5) // magic from TextEditorView
|
metaText.textView.isScrollEnabled = false
|
||||||
return view
|
//metaText.textView.textContainer.lineFragmentPadding = 0
|
||||||
}()
|
//metaText.textView.textContainerInset = .zero
|
||||||
let bioActiveLabel = ActiveLabel(style: .default)
|
metaText.textView.layer.masksToBounds = false
|
||||||
let bioTextEditorView: TextEditorView = {
|
metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment
|
||||||
let textEditorView = TextEditorView()
|
|
||||||
textEditorView.scrollView.isScrollEnabled = false
|
metaText.textView.layer.masksToBounds = true
|
||||||
textEditorView.isScrollEnabled = false
|
metaText.textView.layer.cornerCurve = .continuous
|
||||||
textEditorView.font = .preferredFont(forTextStyle: .body)
|
metaText.textView.layer.cornerRadius = 10
|
||||||
textEditorView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color
|
|
||||||
textEditorView.layer.masksToBounds = true
|
metaText.paragraphStyle = {
|
||||||
textEditorView.layer.cornerCurve = .continuous
|
let style = NSMutableParagraphStyle()
|
||||||
textEditorView.layer.cornerRadius = 10
|
style.lineSpacing = 5
|
||||||
return textEditorView
|
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 {
|
static func createFieldCollectionViewLayout() -> UICollectionViewLayout {
|
||||||
|
@ -405,28 +413,15 @@ extension ProfileHeaderView {
|
||||||
bioContainerView.preservesSuperviewLayoutMargins = true
|
bioContainerView.preservesSuperviewLayoutMargins = true
|
||||||
metaContainerStackView.addArrangedSubview(bioContainerView)
|
metaContainerStackView.addArrangedSubview(bioContainerView)
|
||||||
|
|
||||||
bioContainerStackView.translatesAutoresizingMaskIntoConstraints = false
|
bioMetaText.textView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
bioContainerView.addSubview(bioContainerStackView)
|
bioContainerView.addSubview(bioMetaText.textView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
bioContainerStackView.topAnchor.constraint(equalTo: bioContainerView.topAnchor),
|
bioMetaText.textView.topAnchor.constraint(equalTo: bioContainerView.topAnchor),
|
||||||
bioContainerStackView.leadingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.leadingAnchor),
|
bioMetaText.textView.leadingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.leadingAnchor),
|
||||||
bioContainerStackView.trailingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.trailingAnchor),
|
bioMetaText.textView.trailingAnchor.constraint(equalTo: bioContainerView.readableContentGuide.trailingAnchor),
|
||||||
bioContainerStackView.bottomAnchor.constraint(equalTo: bioContainerView.bottomAnchor),
|
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
|
fieldCollectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
metaContainerStackView.addArrangedSubview(fieldCollectionView)
|
metaContainerStackView.addArrangedSubview(fieldCollectionView)
|
||||||
fieldCollectionViewHeightLayoutConstraint = fieldCollectionView.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh)
|
fieldCollectionViewHeightLayoutConstraint = fieldCollectionView.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh)
|
||||||
|
@ -445,7 +440,7 @@ extension ProfileHeaderView {
|
||||||
bringSubviewToFront(bannerContainerView)
|
bringSubviewToFront(bannerContainerView)
|
||||||
bringSubviewToFront(nameContainerStackView)
|
bringSubviewToFront(nameContainerStackView)
|
||||||
|
|
||||||
bioActiveLabel.delegate = self
|
bioMetaText.textView.linkDelegate = self
|
||||||
|
|
||||||
let avatarImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
let avatarImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
avatarImageView.addGestureRecognizer(avatarImageViewSingleTapGestureRecognizer)
|
avatarImageView.addGestureRecognizer(avatarImageViewSingleTapGestureRecognizer)
|
||||||
|
@ -479,9 +474,8 @@ extension ProfileHeaderView {
|
||||||
nameMetaText.textView.alpha = 1
|
nameMetaText.textView.alpha = 1
|
||||||
nameTextField.alpha = 0
|
nameTextField.alpha = 0
|
||||||
nameTextField.isEnabled = false
|
nameTextField.isEnabled = false
|
||||||
bioActiveLabelContainer.isHidden = false
|
bioMetaText.textView.backgroundColor = .clear
|
||||||
bioTextEditorView.isHidden = true
|
|
||||||
|
|
||||||
animator.addAnimations {
|
animator.addAnimations {
|
||||||
self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundNormalColor
|
self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundNormalColor
|
||||||
self.nameTextFieldBackgroundView.backgroundColor = .clear
|
self.nameTextFieldBackgroundView.backgroundColor = .clear
|
||||||
|
@ -494,17 +488,15 @@ extension ProfileHeaderView {
|
||||||
nameMetaText.textView.alpha = 0
|
nameMetaText.textView.alpha = 0
|
||||||
nameTextField.isEnabled = true
|
nameTextField.isEnabled = true
|
||||||
nameTextField.alpha = 1
|
nameTextField.alpha = 1
|
||||||
bioActiveLabelContainer.isHidden = true
|
|
||||||
bioTextEditorView.isHidden = false
|
|
||||||
|
|
||||||
editAvatarBackgroundView.isHidden = false
|
editAvatarBackgroundView.isHidden = false
|
||||||
editAvatarBackgroundView.alpha = 0
|
editAvatarBackgroundView.alpha = 0
|
||||||
bioTextEditorView.backgroundColor = .clear
|
bioMetaText.textView.backgroundColor = .clear
|
||||||
animator.addAnimations {
|
animator.addAnimations {
|
||||||
self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundEditingColor
|
self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundEditingColor
|
||||||
self.nameTextFieldBackgroundView.backgroundColor = Asset.Scene.Profile.Banner.nameEditBackgroundGray.color
|
self.nameTextFieldBackgroundView.backgroundColor = Asset.Scene.Profile.Banner.nameEditBackgroundGray.color
|
||||||
self.editAvatarBackgroundView.alpha = 1
|
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
|
// MARK: - MetaTextViewDelegate
|
||||||
extension ProfileHeaderView: ActiveLabelDelegate {
|
extension ProfileHeaderView: MetaTextViewDelegate {
|
||||||
func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) {
|
func metaTextView(_ metaTextView: MetaTextView, didSelectMeta meta: Meta) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select entity: %s", ((#file as NSString).lastPathComponent), #line, #function, entity.primaryText)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select entity", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
delegate?.profileHeaderView(self, activeLabel: activeLabel, entityDidPressed: entity)
|
delegate?.profileHeaderView(self, metaTextView: metaTextView, metaDidPressed: meta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import ActiveLabel
|
import MastodonMeta
|
||||||
|
import MetaTextKit
|
||||||
|
|
||||||
final class ProfileViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
final class ProfileViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||||
|
|
||||||
|
@ -316,20 +317,26 @@ extension ProfileViewController {
|
||||||
// bind view model
|
// bind view model
|
||||||
Publishers.CombineLatest3(
|
Publishers.CombineLatest3(
|
||||||
viewModel.name,
|
viewModel.name,
|
||||||
viewModel.emojiDict,
|
viewModel.emojiMeta,
|
||||||
viewModel.statusesCount
|
viewModel.statusesCount
|
||||||
)
|
)
|
||||||
.receive(on: DispatchQueue.main)
|
.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 self = self else { return }
|
||||||
guard let title = name, let statusesCount = statusesCount,
|
guard let title = name, let statusesCount = statusesCount,
|
||||||
let formattedStatusCount = MastodonMetricFormatter().string(from: statusesCount) else {
|
let formattedStatusCount = MastodonMetricFormatter().string(from: statusesCount) else {
|
||||||
self.titleView.isHidden = true
|
self.titleView.isHidden = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let subtitle = L10n.Plural.Count.MetricFormatted.post(formattedStatusCount, statusesCount)
|
|
||||||
self.titleView.update(title: title, subtitle: subtitle, emojiDict: emojiDict)
|
|
||||||
self.titleView.isHidden = false
|
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)
|
.store(in: &disposeBag)
|
||||||
viewModel.name
|
viewModel.name
|
||||||
|
@ -391,9 +398,9 @@ extension ProfileViewController {
|
||||||
viewModel.accountForEdit
|
viewModel.accountForEdit
|
||||||
.assign(to: \.value, on: profileHeaderViewController.viewModel.accountForEdit)
|
.assign(to: \.value, on: profileHeaderViewController.viewModel.accountForEdit)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
viewModel.emojiDict
|
viewModel.emojiMeta
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.assign(to: \.value, on: profileHeaderViewController.viewModel.emojiDict)
|
.assign(to: \.value, on: profileHeaderViewController.viewModel.emojiMeta)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
viewModel.username
|
viewModel.username
|
||||||
.map { username in username.flatMap { "@" + $0 } ?? " " }
|
.map { username in username.flatMap { "@" + $0 } ?? " " }
|
||||||
|
@ -695,7 +702,7 @@ extension ProfileViewController: UIScrollViewDelegate {
|
||||||
|
|
||||||
// MARK: - ProfileHeaderViewControllerDelegate
|
// MARK: - ProfileHeaderViewControllerDelegate
|
||||||
extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||||
|
|
||||||
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView) {
|
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView) {
|
||||||
guard let scrollView = (profileSegmentedViewController.pagingViewController.currentViewController as? UserTimelineViewController)?.scrollView else {
|
guard let scrollView = (profileSegmentedViewController.pagingViewController.currentViewController as? UserTimelineViewController)?.scrollView else {
|
||||||
// assertionFailure()
|
// assertionFailure()
|
||||||
|
@ -712,23 +719,24 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, profileFieldCollectionViewCell: ProfileFieldCollectionViewCell, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) {
|
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, profileFieldCollectionViewCell: ProfileFieldCollectionViewCell, metaLabel: MetaLabel, didSelectMeta meta: Meta) {
|
||||||
// handle profile fields interaction
|
switch meta {
|
||||||
switch entity.type {
|
|
||||||
case .url(_, _, let url, _):
|
case .url(_, _, let url, _):
|
||||||
guard let url = URL(string: url) else { return }
|
guard let url = URL(string: url) else { return }
|
||||||
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
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)
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag)
|
||||||
coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
|
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 {
|
guard let href = userInfo?["href"] as? String else {
|
||||||
// currently we cannot present profile scene without userID
|
// currently we cannot present profile scene without userID
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let url = URL(string: href) else { return }
|
guard let url = URL(string: href) else { return }
|
||||||
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||||
default:
|
case .email:
|
||||||
|
break
|
||||||
|
case .emoji:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -758,7 +766,7 @@ extension ProfileViewController: ProfilePagingViewControllerDelegate {
|
||||||
|
|
||||||
// MARK: - ProfileHeaderViewDelegate
|
// MARK: - ProfileHeaderViewDelegate
|
||||||
extension ProfileViewController: ProfileHeaderViewDelegate {
|
extension ProfileViewController: ProfileHeaderViewDelegate {
|
||||||
|
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) {
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) {
|
||||||
guard let mastodonUser = viewModel.mastodonUser.value else { return }
|
guard let mastodonUser = viewModel.mastodonUser.value else { return }
|
||||||
guard let avatar = imageView.image 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) {
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, metaTextView: MetaTextView, metaDidPressed meta: Meta) {
|
||||||
switch entity.type {
|
switch meta {
|
||||||
case .url(_, _, let url, _):
|
case .url(_, _, let url, _):
|
||||||
guard let url = URL(string: url) else { return }
|
guard let url = URL(string: url) else { return }
|
||||||
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
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,
|
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))
|
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)
|
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag)
|
||||||
coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
|
coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
|
||||||
default:
|
case .email, .emoji:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) {
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
// please override this base class
|
// please override this base class
|
||||||
class ProfileViewModel: NSObject {
|
class ProfileViewModel: NSObject {
|
||||||
|
@ -40,7 +41,7 @@ class ProfileViewModel: NSObject {
|
||||||
let followingCount: CurrentValueSubject<Int?, Never>
|
let followingCount: CurrentValueSubject<Int?, Never>
|
||||||
let followersCount: CurrentValueSubject<Int?, Never>
|
let followersCount: CurrentValueSubject<Int?, Never>
|
||||||
let fields: CurrentValueSubject<[Mastodon.Entity.Field], Never>
|
let fields: CurrentValueSubject<[Mastodon.Entity.Field], Never>
|
||||||
let emojiDict: CurrentValueSubject<MastodonStatusContent.EmojiDict, Never>
|
let emojiMeta: CurrentValueSubject<MastodonContent.Emojis, Never>
|
||||||
|
|
||||||
// fulfill this before editing
|
// fulfill this before editing
|
||||||
let accountForEdit = CurrentValueSubject<Mastodon.Entity.Account?, Never>(nil)
|
let accountForEdit = CurrentValueSubject<Mastodon.Entity.Account?, Never>(nil)
|
||||||
|
@ -83,7 +84,7 @@ class ProfileViewModel: NSObject {
|
||||||
self.protected = CurrentValueSubject(mastodonUser?.locked)
|
self.protected = CurrentValueSubject(mastodonUser?.locked)
|
||||||
self.suspended = CurrentValueSubject(mastodonUser?.suspended ?? false)
|
self.suspended = CurrentValueSubject(mastodonUser?.suspended ?? false)
|
||||||
self.fields = CurrentValueSubject(mastodonUser?.fields ?? [])
|
self.fields = CurrentValueSubject(mastodonUser?.fields ?? [])
|
||||||
self.emojiDict = CurrentValueSubject(mastodonUser?.emojiDict ?? [:])
|
self.emojiMeta = CurrentValueSubject(mastodonUser?.emojiMeta ?? [:])
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
relationshipActionOptionSet
|
relationshipActionOptionSet
|
||||||
|
@ -258,7 +259,7 @@ extension ProfileViewModel {
|
||||||
self.protected.value = mastodonUser?.locked
|
self.protected.value = mastodonUser?.locked
|
||||||
self.suspended.value = mastodonUser?.suspended ?? false
|
self.suspended.value = mastodonUser?.suspended ?? false
|
||||||
self.fields.value = mastodonUser?.fields ?? []
|
self.fields.value = mastodonUser?.fields ?? []
|
||||||
self.emojiDict.value = mastodonUser?.emojiDict ?? [:]
|
self.emojiMeta.value = mastodonUser?.emojiMeta ?? [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func update(mastodonUser: MastodonUser?, currentMastodonUser: MastodonUser?) {
|
private func update(mastodonUser: MastodonUser?, currentMastodonUser: MastodonUser?) {
|
||||||
|
|
|
@ -11,8 +11,8 @@ import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import TwitterTextEditor
|
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
class ReportViewController: UIViewController, NeedsDependency {
|
class ReportViewController: UIViewController, NeedsDependency {
|
||||||
static let kAnimationDuration: TimeInterval = 0.33
|
static let kAnimationDuration: TimeInterval = 0.33
|
||||||
|
@ -92,6 +92,8 @@ class ReportViewController: UIViewController, NeedsDependency {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var bottomConstraint: NSLayoutConstraint!
|
var bottomConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
|
let titleView = DoubleTitleLabelNavigationBarTitleView()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
@ -267,10 +269,19 @@ class ReportViewController: UIViewController, NeedsDependency {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
navigationItem.title = L10n.Scene.Report.title(
|
navigationItem.titleView = titleView
|
||||||
beReportedUser?.displayNameWithFallback ?? ""
|
if let user = beReportedUser {
|
||||||
)
|
do {
|
||||||
|
let mastodonConent = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojiMeta)
|
||||||
|
let metaContent = try MastodonMetaContent.convert(document: mastodonConent)
|
||||||
|
titleView.update(titleMetaContent: metaContent, subtitle: nil)
|
||||||
|
} catch {
|
||||||
|
let metaContent = PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||||
|
titleView.update(titleMetaContent: metaContent, subtitle: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func switchToStep2Content() {
|
private func switchToStep2Content() {
|
||||||
|
|
|
@ -11,9 +11,8 @@ import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import ActiveLabel
|
|
||||||
import Meta
|
import Meta
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
final class ReportedStatusTableViewCell: UITableViewCell, StatusCell {
|
final class ReportedStatusTableViewCell: UITableViewCell, StatusCell {
|
||||||
|
|
||||||
|
@ -212,9 +211,6 @@ extension ReportedStatusTableViewCell: StatusViewDelegate {
|
||||||
|
|
||||||
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) {
|
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) {
|
func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import CoreDataStack
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
import ActiveLabel
|
import MetaTextKit
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
protocol SearchRecommendAccountsCollectionViewCellDelegate: NSObject {
|
protocol SearchRecommendAccountsCollectionViewCellDelegate: NSObject {
|
||||||
func followButtonDidPressed(clickedUser: MastodonUser)
|
func followButtonDidPressed(clickedUser: MastodonUser)
|
||||||
|
@ -43,14 +44,7 @@ class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
||||||
|
|
||||||
let displayNameLabel: ActiveLabel = {
|
let displayNameLabel = MetaLabel(style: .recommendAccountName)
|
||||||
let label = ActiveLabel(style: .statusName)
|
|
||||||
label.textColor = .white
|
|
||||||
label.textAlignment = .center
|
|
||||||
label.font = .systemFont(ofSize: 18, weight: .semibold)
|
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
let acctLabel: UILabel = {
|
let acctLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
@ -165,7 +159,14 @@ extension SearchRecommendAccountsCollectionViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func config(with mastodonUser: MastodonUser) {
|
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
|
acctLabel.text = "@" + mastodonUser.acct
|
||||||
avatarImageView.af.setImage(
|
avatarImageView.af.setImage(
|
||||||
withURL: URL(string: mastodonUser.avatar)!,
|
withURL: URL(string: mastodonUser.avatar)!,
|
||||||
|
|
|
@ -11,6 +11,8 @@ import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
|
import MetaTextKit
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
final class SearchResultTableViewCell: UITableViewCell {
|
final class SearchResultTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
|
@ -22,13 +24,7 @@ final class SearchResultTableViewCell: UITableViewCell {
|
||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let _titleLabel: UILabel = {
|
let _titleLabel = MetaLabel(style: .statusName)
|
||||||
let label = UILabel()
|
|
||||||
label.textColor = Asset.Colors.brandBlue.color
|
|
||||||
label.font = .systemFont(ofSize: 17, weight: .semibold)
|
|
||||||
label.lineBreakMode = .byTruncatingTail
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
let _subTitleLabel: UILabel = {
|
let _subTitleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
@ -155,13 +151,28 @@ extension SearchResultTableViewCell {
|
||||||
|
|
||||||
func config(with account: Mastodon.Entity.Account) {
|
func config(with account: Mastodon.Entity.Account) {
|
||||||
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL()))
|
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
|
_subTitleLabel.text = account.acct
|
||||||
}
|
}
|
||||||
|
|
||||||
func config(with account: MastodonUser) {
|
func config(with account: MastodonUser) {
|
||||||
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL()))
|
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
|
_subTitleLabel.text = account.acct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import ActiveLabel
|
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MetaTextKit
|
||||||
|
import MastodonMeta
|
||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
|
|
||||||
class SettingsViewController: UIViewController, NeedsDependency {
|
class SettingsViewController: UIViewController, NeedsDependency {
|
||||||
|
@ -103,12 +104,7 @@ class SettingsViewController: UIViewController, NeedsDependency {
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let tableFooterActiveLabel: ActiveLabel = {
|
let tableFooterLabel = MetaLabel(style: .settingTableFooter)
|
||||||
let label = ActiveLabel(style: .default)
|
|
||||||
label.adjustsFontForContentSizeCategory = true
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
lazy var tableFooterView: UIView = {
|
lazy var tableFooterView: UIView = {
|
||||||
// init with a frame to fix a conflict ('UIView-Encapsulated-Layout-Height' UIStackView:0x7ffe41e47da0.height == 0)
|
// 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))
|
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.axis = .vertical
|
||||||
view.alignment = .center
|
view.alignment = .center
|
||||||
|
|
||||||
tableFooterActiveLabel.delegate = self
|
tableFooterLabel.linkDelegate = self
|
||||||
view.addArrangedSubview(tableFooterActiveLabel)
|
view.addArrangedSubview(tableFooterLabel)
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -199,7 +195,15 @@ class SettingsViewController: UIViewController, NeedsDependency {
|
||||||
let version = instance?.version ?? "-"
|
let version = instance?.version ?? "-"
|
||||||
let link = #"<a href="https://github.com/mastodon/mastodon">mastodon/mastodon</a>"#
|
let link = #"<a href="https://github.com/mastodon/mastodon">mastodon/mastodon</a>"#
|
||||||
let content = L10n.Scene.Settings.Footer.mastodonDescription(link, version)
|
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)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
@ -365,6 +369,8 @@ extension SettingsViewController: UITableViewDelegate {
|
||||||
// do nothing
|
// do nothing
|
||||||
break
|
break
|
||||||
case .boringZone(let link), .spicyZone(let link):
|
case .boringZone(let link), .spicyZone(let link):
|
||||||
|
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||||
|
feedbackGenerator.impactOccurred()
|
||||||
switch link {
|
switch link {
|
||||||
case .accountSettings:
|
case .accountSettings:
|
||||||
guard let box = context.authenticationService.activeMastodonAuthenticationBox.value,
|
guard let box = context.authenticationService.activeMastodonAuthenticationBox.value,
|
||||||
|
@ -395,6 +401,7 @@ extension SettingsViewController: UITableViewDelegate {
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
case .signOut:
|
case .signOut:
|
||||||
|
feedbackGenerator.impactOccurred()
|
||||||
alertToSignout()
|
alertToSignout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,7 +447,8 @@ extension SettingsViewController: SettingsAppearanceTableViewCellDelegate {
|
||||||
setting.update(appearanceRaw: appearanceMode.rawValue)
|
setting.update(appearanceRaw: appearanceMode.rawValue)
|
||||||
}
|
}
|
||||||
.sink { _ in
|
.sink { _ in
|
||||||
// do nothing
|
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||||
|
feedbackGenerator.impactOccurred()
|
||||||
}.store(in: &disposeBag)
|
}.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,6 +490,8 @@ extension SettingsViewController: SettingsToggleCellDelegate {
|
||||||
setting.update(preferredTrueBlackDarkMode: isOn)
|
setting.update(preferredTrueBlackDarkMode: isOn)
|
||||||
case .disableAvatarAnimation:
|
case .disableAvatarAnimation:
|
||||||
setting.update(preferredStaticAvatar: isOn)
|
setting.update(preferredStaticAvatar: isOn)
|
||||||
|
case .disableEmojiAnimation:
|
||||||
|
setting.update(preferredStaticEmoji: isOn)
|
||||||
case .useDefaultBrowser:
|
case .useDefaultBrowser:
|
||||||
setting.update(preferredUsingDefaultBrowser: isOn)
|
setting.update(preferredUsingDefaultBrowser: isOn)
|
||||||
}
|
}
|
||||||
|
@ -494,6 +504,8 @@ extension SettingsViewController: SettingsToggleCellDelegate {
|
||||||
ThemeService.shared.set(themeName: isOn ? .system : .mastodon)
|
ThemeService.shared.set(themeName: isOn ? .system : .mastodon)
|
||||||
case .disableAvatarAnimation:
|
case .disableAvatarAnimation:
|
||||||
UserDefaults.shared.preferredStaticAvatar = isOn
|
UserDefaults.shared.preferredStaticAvatar = isOn
|
||||||
|
case .disableEmojiAnimation:
|
||||||
|
UserDefaults.shared.preferredStaticEmoji = isOn
|
||||||
case .useDefaultBrowser:
|
case .useDefaultBrowser:
|
||||||
UserDefaults.shared.preferredUsingDefaultBrowser = isOn
|
UserDefaults.shared.preferredUsingDefaultBrowser = isOn
|
||||||
}
|
}
|
||||||
|
@ -510,13 +522,16 @@ extension SettingsViewController: SettingsToggleCellDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SettingsViewController: ActiveLabelDelegate {
|
// MARK: - MetaLabelDelegate
|
||||||
func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) {
|
extension SettingsViewController: MetaLabelDelegate {
|
||||||
coordinator.present(
|
func metaLabel(_ metaLabel: MetaLabel, didSelectMeta meta: Meta) {
|
||||||
scene: .safari(url: URL(string: "https://github.com/mastodon/mastodon")!),
|
switch meta {
|
||||||
from: self,
|
case .url(_, _, let url, _):
|
||||||
transition: .safariPresent(animated: true, completion: nil)
|
guard let url = URL(string: url) else { return }
|
||||||
)
|
coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil))
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,11 +121,9 @@ extension SettingsViewModel {
|
||||||
|
|
||||||
// preference
|
// preference
|
||||||
snapshot.appendSections([.preference])
|
snapshot.appendSections([.preference])
|
||||||
let preferenceItems: [SettingsItem] = [
|
let preferenceItems: [SettingsItem] = SettingsItem.PreferenceType.allCases.map { preferenceType in
|
||||||
.preference(settingObjectID: setting.objectID, preferenceType: .darkMode),
|
SettingsItem.preference(settingObjectID: setting.objectID, preferenceType: preferenceType)
|
||||||
.preference(settingObjectID: setting.objectID, preferenceType: .disableAvatarAnimation),
|
}
|
||||||
.preference(settingObjectID: setting.objectID, preferenceType: .useDefaultBrowser),
|
|
||||||
]
|
|
||||||
snapshot.appendItems(preferenceItems,toSection: .preference)
|
snapshot.appendItems(preferenceItems,toSection: .preference)
|
||||||
|
|
||||||
// boring zone
|
// boring zone
|
||||||
|
|
|
@ -6,19 +6,14 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import ActiveLabel
|
import Meta
|
||||||
|
import MetaTextKit
|
||||||
|
|
||||||
final class DoubleTitleLabelNavigationBarTitleView: UIView {
|
final class DoubleTitleLabelNavigationBarTitleView: UIView {
|
||||||
|
|
||||||
let containerView = UIStackView()
|
let containerView = UIStackView()
|
||||||
|
|
||||||
let titleLabel: ActiveLabel = {
|
let titleLabel = MetaLabel(style: .titleView)
|
||||||
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 subtitleLabel: UILabel = {
|
let subtitleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
@ -58,9 +53,18 @@ extension DoubleTitleLabelNavigationBarTitleView {
|
||||||
containerView.addArrangedSubview(titleLabel)
|
containerView.addArrangedSubview(titleLabel)
|
||||||
containerView.addArrangedSubview(subtitleLabel)
|
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) {
|
func update(titleMetaContent: MetaContent, subtitle: String?) {
|
||||||
titleLabel.configure(content: title, emojiDict: emojiDict)
|
titleLabel.configure(content: titleMetaContent)
|
||||||
|
update(subtitle: subtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(subtitle: String?) {
|
||||||
if let subtitle = subtitle {
|
if let subtitle = subtitle {
|
||||||
subtitleLabel.text = subtitle
|
subtitleLabel.text = subtitle
|
||||||
subtitleLabel.isHidden = false
|
subtitleLabel.isHidden = false
|
||||||
|
|
|
@ -9,10 +9,9 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import AVKit
|
import AVKit
|
||||||
import ActiveLabel
|
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
import Meta
|
import Meta
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
|
@ -26,7 +25,6 @@ protocol StatusViewDelegate: AnyObject {
|
||||||
func statusView(_ statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
func statusView(_ statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
||||||
func statusView(_ statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
func statusView(_ statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
||||||
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
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)
|
func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,12 +79,7 @@ final class StatusView: UIView {
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let headerInfoLabel: ActiveLabel = {
|
let headerInfoLabel = MetaLabel(style: .statusHeader)
|
||||||
let label = ActiveLabel(style: .statusHeader)
|
|
||||||
label.text = "Bob reblogged"
|
|
||||||
label.layer.masksToBounds = false
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
let avatarView: UIView = {
|
let avatarView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
@ -98,8 +91,8 @@ final class StatusView: UIView {
|
||||||
let avatarButton = AvatarButton()
|
let avatarButton = AvatarButton()
|
||||||
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
|
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
|
||||||
|
|
||||||
let nameLabel: ActiveLabel = {
|
let nameMetaLabel: MetaLabel = {
|
||||||
let label = ActiveLabel(style: .statusName)
|
let label = MetaLabel(style: .statusName)
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -220,7 +213,7 @@ final class StatusView: UIView {
|
||||||
metaText.textView.layer.masksToBounds = false
|
metaText.textView.layer.masksToBounds = false
|
||||||
metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment
|
metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment
|
||||||
|
|
||||||
let paragraphStyle: NSMutableParagraphStyle = {
|
metaText.paragraphStyle = {
|
||||||
let style = NSMutableParagraphStyle()
|
let style = NSMutableParagraphStyle()
|
||||||
style.lineSpacing = 5
|
style.lineSpacing = 5
|
||||||
style.paragraphSpacing = 8
|
style.paragraphSpacing = 8
|
||||||
|
@ -229,12 +222,10 @@ final class StatusView: UIView {
|
||||||
metaText.textAttributes = [
|
metaText.textAttributes = [
|
||||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
||||||
.foregroundColor: Asset.Colors.Label.primary.color,
|
.foregroundColor: Asset.Colors.Label.primary.color,
|
||||||
.paragraphStyle: paragraphStyle,
|
|
||||||
]
|
]
|
||||||
metaText.linkAttributes = [
|
metaText.linkAttributes = [
|
||||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
||||||
.foregroundColor: Asset.Colors.brandBlue.color,
|
.foregroundColor: Asset.Colors.brandBlue.color,
|
||||||
.paragraphStyle: paragraphStyle,
|
|
||||||
]
|
]
|
||||||
return metaText
|
return metaText
|
||||||
}()
|
}()
|
||||||
|
@ -296,6 +287,7 @@ extension StatusView {
|
||||||
headerContainerStackView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor),
|
headerContainerStackView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor),
|
||||||
headerContainerView.bottomAnchor.constraint(equalTo: headerContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh),
|
headerContainerView.bottomAnchor.constraint(equalTo: headerContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh),
|
||||||
])
|
])
|
||||||
|
headerContainerStackView.setContentCompressionResistancePriority(.required - 5, for: .vertical)
|
||||||
containerStackView.addArrangedSubview(headerContainerView)
|
containerStackView.addArrangedSubview(headerContainerView)
|
||||||
defer {
|
defer {
|
||||||
containerStackView.bringSubviewToFront(headerContainerView)
|
containerStackView.bringSubviewToFront(headerContainerView)
|
||||||
|
@ -343,24 +335,27 @@ extension StatusView {
|
||||||
titleContainerStackView.axis = .horizontal
|
titleContainerStackView.axis = .horizontal
|
||||||
titleContainerStackView.alignment = .center
|
titleContainerStackView.alignment = .center
|
||||||
titleContainerStackView.spacing = 4
|
titleContainerStackView.spacing = 4
|
||||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
nameMetaLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
titleContainerStackView.addArrangedSubview(nameLabel)
|
titleContainerStackView.addArrangedSubview(nameMetaLabel)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
nameLabel.heightAnchor.constraint(equalToConstant: 22).priority(.defaultHigh),
|
nameMetaLabel.heightAnchor.constraint(equalToConstant: 22).priority(.defaultHigh),
|
||||||
])
|
])
|
||||||
titleContainerStackView.alignment = .firstBaseline
|
|
||||||
titleContainerStackView.addArrangedSubview(nameTrialingDotLabel)
|
titleContainerStackView.addArrangedSubview(nameTrialingDotLabel)
|
||||||
titleContainerStackView.addArrangedSubview(dateLabel)
|
titleContainerStackView.addArrangedSubview(dateLabel)
|
||||||
titleContainerStackView.addArrangedSubview(UIView()) // padding
|
let padding = UIView()
|
||||||
|
padding.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
titleContainerStackView.addArrangedSubview(padding) // padding
|
||||||
titleContainerStackView.addArrangedSubview(visibilityImageView)
|
titleContainerStackView.addArrangedSubview(visibilityImageView)
|
||||||
nameLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
|
nameMetaLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
|
||||||
nameTrialingDotLabel.setContentHuggingPriority(.defaultHigh + 2, for: .horizontal)
|
nameTrialingDotLabel.setContentHuggingPriority(.defaultHigh + 2, for: .horizontal)
|
||||||
nameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal)
|
nameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal)
|
||||||
dateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
dateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||||
dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
dateLabel.setContentCompressionResistancePriority(.required - 10, for: .horizontal)
|
||||||
visibilityImageView.setContentHuggingPriority(.required - 1, 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.setContentHuggingPriority(.required - 1, for: .vertical)
|
||||||
visibilityImageView.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
|
||||||
|
|
||||||
// subtitle container: [username]
|
// subtitle container: [username]
|
||||||
let subtitleContainerStackView = UIStackView()
|
let subtitleContainerStackView = UIStackView()
|
||||||
|
@ -424,17 +419,21 @@ extension StatusView {
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
pollTableViewHeightLayoutConstraint,
|
pollTableViewHeightLayoutConstraint,
|
||||||
])
|
])
|
||||||
|
|
||||||
statusPollTableViewHeightObservation = pollTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] tableView, _ in
|
// statusPollTableViewHeightObservation = pollTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] tableView, _ in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
guard self.pollTableView.contentSize.height != .zero else {
|
// guard self.pollTableView.contentSize.height != .zero else {
|
||||||
self.pollTableViewHeightLayoutConstraint.constant = 44
|
// self.pollTableViewHeightLayoutConstraint.constant = 44
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
self.pollTableViewHeightLayoutConstraint.constant = self.pollTableView.contentSize.height
|
// self.pollTableViewHeightLayoutConstraint.constant = self.pollTableView.contentSize.height
|
||||||
})
|
// })
|
||||||
|
|
||||||
|
pollStatusStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
statusContainerStackView.addArrangedSubview(pollStatusStackView)
|
statusContainerStackView.addArrangedSubview(pollStatusStackView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
pollStatusStackView.heightAnchor.constraint(equalToConstant: 30).priority(.required - 10)
|
||||||
|
])
|
||||||
pollStatusStackView.axis = .horizontal
|
pollStatusStackView.axis = .horizontal
|
||||||
pollStatusStackView.addArrangedSubview(pollVoteCountLabel)
|
pollStatusStackView.addArrangedSubview(pollVoteCountLabel)
|
||||||
pollStatusStackView.addArrangedSubview(pollStatusDotLabel)
|
pollStatusStackView.addArrangedSubview(pollStatusDotLabel)
|
||||||
|
@ -561,11 +560,10 @@ extension StatusView {
|
||||||
|
|
||||||
// MARK: - MetaTextViewDelegate
|
// MARK: - MetaTextViewDelegate
|
||||||
extension StatusView: 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)")
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
switch metaTextView {
|
switch metaTextView {
|
||||||
case contentMetaText.textView:
|
case contentMetaText.textView:
|
||||||
guard let meta = Meta(url: link) else { return }
|
|
||||||
delegate?.statusView(self, metaText: contentMetaText, didSelectMeta: meta)
|
delegate?.statusView(self, metaText: contentMetaText, didSelectMeta: meta)
|
||||||
default:
|
default:
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
|
@ -598,14 +596,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
|
// MARK: - ContentWarningOverlayViewDelegate
|
||||||
extension StatusView: ContentWarningOverlayViewDelegate {
|
extension StatusView: ContentWarningOverlayViewDelegate {
|
||||||
func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) {
|
func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) {
|
||||||
|
|
|
@ -11,11 +11,10 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import ActiveLabel
|
|
||||||
import func AVFoundation.AVMakeRect
|
import func AVFoundation.AVMakeRect
|
||||||
|
|
||||||
protocol StatusNodeDelegate: AnyObject {
|
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 {
|
final class StatusNode: ASCellNode {
|
||||||
|
@ -29,21 +28,21 @@ final class StatusNode: ASCellNode {
|
||||||
static let avatarImageSize = CGSize(width: 42, height: 42)
|
static let avatarImageSize = CGSize(width: 42, height: 42)
|
||||||
static let avatarImageCornerRadius: CGFloat = 4
|
static let avatarImageCornerRadius: CGFloat = 4
|
||||||
|
|
||||||
static let statusContentAppearance: MastodonStatusContent.Appearance = {
|
// static let statusContentAppearance: MastodonStatusContent.Appearance = {
|
||||||
let linkAttributes: [NSAttributedString.Key: Any] = [
|
// let linkAttributes: [NSAttributedString.Key: Any] = [
|
||||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
||||||
.foregroundColor: Asset.Colors.brandBlue.color
|
// .foregroundColor: Asset.Colors.brandBlue.color
|
||||||
]
|
// ]
|
||||||
return MastodonStatusContent.Appearance(
|
// return MastodonStatusContent.Appearance(
|
||||||
attributes: [
|
// attributes: [
|
||||||
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
// .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
||||||
.foregroundColor: Asset.Colors.Label.primary.color
|
// .foregroundColor: Asset.Colors.Label.primary.color
|
||||||
],
|
// ],
|
||||||
urlAttributes: linkAttributes,
|
// urlAttributes: linkAttributes,
|
||||||
hashtagAttributes: linkAttributes,
|
// hashtagAttributes: linkAttributes,
|
||||||
mentionAttributes: linkAttributes
|
// mentionAttributes: linkAttributes
|
||||||
)
|
// )
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
let avatarImageNode: ASNetworkImageNode = {
|
let avatarImageNode: ASNetworkImageNode = {
|
||||||
let node = ASNetworkImageNode()
|
let node = ASNetworkImageNode()
|
||||||
|
@ -112,13 +111,14 @@ final class StatusNode: ASCellNode {
|
||||||
.font: UIFont.systemFont(ofSize: 15, weight: .regular)
|
.font: UIFont.systemFont(ofSize: 15, weight: .regular)
|
||||||
])
|
])
|
||||||
|
|
||||||
statusContentTextNode.metaEditableTextNodeDelegate = self
|
// FIXME:
|
||||||
if let parseResult = try? MastodonStatusContent.parse(
|
// statusContentTextNode.metaEditableTextNodeDelegate = self
|
||||||
content: (status.reblog ?? status).content,
|
// if let parseResult = try? MastodonStatusContent.parse(
|
||||||
emojiDict: (status.reblog ?? status).emojiDict
|
// content: (status.reblog ?? status).content,
|
||||||
) {
|
// emojiDict: (status.reblog ?? status).emojiDict
|
||||||
statusContentTextNode.attributedText = parseResult.trimmedAttributedString(appearance: StatusNode.statusContentAppearance)
|
// ) {
|
||||||
}
|
// statusContentTextNode.attributedText = parseResult.trimmedAttributedString(appearance: StatusNode.statusContentAppearance)
|
||||||
|
// }
|
||||||
|
|
||||||
for imageNode in mediaMultiplexImageNodes {
|
for imageNode in mediaMultiplexImageNodes {
|
||||||
imageNode.dataSource = self
|
imageNode.dataSource = self
|
||||||
|
@ -200,29 +200,19 @@ final class StatusNode: ASCellNode {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//extension StatusNode: ASImageDownloaderProtocol {
|
// MARK: - ASEditableTextNodeDelegate
|
||||||
// func downloadImage(with URL: URL, callbackQueue: DispatchQueue, downloadProgress: ASImageDownloaderProgress?, completion: @escaping ASImageDownloaderCompletion) -> Any? {
|
//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
|
||||||
// func cancelImageDownload(forIdentifier downloadIdentifier: Any) {
|
// }
|
||||||
//
|
// 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
|
// MARK: - ASMultiplexImageNodeDataSource
|
||||||
extension StatusNode: ASMultiplexImageNodeDataSource {
|
extension StatusNode: ASMultiplexImageNodeDataSource {
|
||||||
func multiplexImageNode(_ imageNode: ASMultiplexImageNode, urlForImageIdentifier imageIdentifier: ASImageIdentifier) -> URL? {
|
func multiplexImageNode(_ imageNode: ASMultiplexImageNode, urlForImageIdentifier imageIdentifier: ASImageIdentifier) -> URL? {
|
||||||
|
|
|
@ -10,6 +10,8 @@ import Combine
|
||||||
|
|
||||||
final class PollOptionTableViewCell: UITableViewCell {
|
final class PollOptionTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
|
static let height: CGFloat = PollOptionView.height
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
let pollOptionView = PollOptionView()
|
let pollOptionView = PollOptionView()
|
||||||
|
|
|
@ -11,9 +11,8 @@ import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import ActiveLabel
|
|
||||||
import Meta
|
import Meta
|
||||||
import MetaTextView
|
import MetaTextKit
|
||||||
|
|
||||||
protocol StatusTableViewCellDelegate: AnyObject {
|
protocol StatusTableViewCellDelegate: AnyObject {
|
||||||
var context: AppContext! { get }
|
var context: AppContext! { get }
|
||||||
|
@ -27,7 +26,6 @@ protocol StatusTableViewCellDelegate: AnyObject {
|
||||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton)
|
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton)
|
||||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
||||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
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, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
|
||||||
|
|
||||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
||||||
|
@ -328,10 +326,6 @@ extension StatusTableViewCell: StatusViewDelegate {
|
||||||
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) {
|
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) {
|
||||||
delegate?.statusTableViewCell(self, statusView: statusView, pollVoteButtonPressed: button)
|
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) {
|
func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) {
|
||||||
delegate?.statusTableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta)
|
delegate?.statusTableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta)
|
||||||
|
|
|
@ -11,7 +11,8 @@ import CoreDataStack
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
import ActiveLabel
|
import MetaTextKit
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
protocol SuggestionAccountTableViewCellDelegate: AnyObject {
|
protocol SuggestionAccountTableViewCellDelegate: AnyObject {
|
||||||
func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell)
|
func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell)
|
||||||
|
@ -29,13 +30,7 @@ final class SuggestionAccountTableViewCell: UITableViewCell {
|
||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let titleLabel: ActiveLabel = {
|
let titleLabel = MetaLabel(style: .statusName)
|
||||||
let label = ActiveLabel(style: .statusName)
|
|
||||||
label.textColor = Asset.Colors.brandBlue.color
|
|
||||||
label.font = .systemFont(ofSize: 17, weight: .semibold)
|
|
||||||
label.lineBreakMode = .byTruncatingTail
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
let subTitleLabel: UILabel = {
|
let subTitleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
@ -152,8 +147,15 @@ extension SuggestionAccountTableViewCell {
|
||||||
imageTransition: .crossDissolve(0.2)
|
imageTransition: .crossDissolve(0.2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
titleLabel.configure(content: account.displayNameWithFallback, emojiDict: account.emojiDict)
|
let mastodonContent = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojiMeta)
|
||||||
subTitleLabel.text = account.acct
|
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.isSelected = isSelected
|
||||||
button.publisher(for: .touchUpInside)
|
button.publisher(for: .touchUpInside)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import AVKit
|
import AVKit
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
final class ThreadViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
final class ThreadViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||||
|
|
||||||
|
@ -84,18 +85,27 @@ extension ThreadViewController {
|
||||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
viewModel.navigationBarTitle
|
Publishers.CombineLatest(
|
||||||
.receive(on: DispatchQueue.main)
|
viewModel.navigationBarTitle,
|
||||||
.sink { [weak self] tuple in
|
viewModel.navigationBarTitleEmojiMeta
|
||||||
guard let self = self else { return }
|
)
|
||||||
guard let (title, emojiDict) = tuple else {
|
.receive(on: DispatchQueue.main)
|
||||||
self.titleView.update(title: L10n.Scene.Thread.backTitle, subtitle: nil, emojiDict: [:])
|
.sink { [weak self] title, emojiMeta in
|
||||||
return
|
guard let self = self else { return }
|
||||||
}
|
guard let title = title else {
|
||||||
self.titleView.update(title: title, subtitle: nil, emojiDict: emojiDict)
|
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) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
class ThreadViewModel {
|
class ThreadViewModel {
|
||||||
|
|
||||||
|
@ -45,7 +46,8 @@ class ThreadViewModel {
|
||||||
let ancestorItems = CurrentValueSubject<[Item], Never>([])
|
let ancestorItems = CurrentValueSubject<[Item], Never>([])
|
||||||
let descendantNodes = CurrentValueSubject<[LeafNode], Never>([])
|
let descendantNodes = CurrentValueSubject<[LeafNode], Never>([])
|
||||||
let descendantItems = CurrentValueSubject<[Item], Never>([])
|
let descendantItems = CurrentValueSubject<[Item], Never>([])
|
||||||
let navigationBarTitle: CurrentValueSubject<(String, MastodonStatusContent.EmojiDict)?, Never>
|
let navigationBarTitle: CurrentValueSubject<String?, Never>
|
||||||
|
let navigationBarTitleEmojiMeta: CurrentValueSubject<MastodonContent.Emojis, Never>
|
||||||
|
|
||||||
init(context: AppContext, optionalStatus: Status?) {
|
init(context: AppContext, optionalStatus: Status?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
@ -53,8 +55,8 @@ class ThreadViewModel {
|
||||||
self.rootItem = CurrentValueSubject(optionalStatus.flatMap { Item.root(statusObjectID: $0.objectID, attribute: Item.StatusAttribute()) })
|
self.rootItem = CurrentValueSubject(optionalStatus.flatMap { Item.root(statusObjectID: $0.objectID, attribute: Item.StatusAttribute()) })
|
||||||
self.existStatusFetchedResultsController = StatusFetchedResultsController(managedObjectContext: context.managedObjectContext, domain: nil, additionalTweetPredicate: nil)
|
self.existStatusFetchedResultsController = StatusFetchedResultsController(managedObjectContext: context.managedObjectContext, domain: nil, additionalTweetPredicate: nil)
|
||||||
self.navigationBarTitle = CurrentValueSubject(
|
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
|
// bind fetcher domain
|
||||||
context.authenticationService.activeMastodonAuthenticationBox
|
context.authenticationService.activeMastodonAuthenticationBox
|
||||||
|
@ -85,7 +87,8 @@ class ThreadViewModel {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.rootNode.value = RootNode(domain: status.domain, statusID: status.id, replyToID: status.inReplyToID)
|
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)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -211,9 +211,15 @@ extension SettingService {
|
||||||
UserDefaults.shared.preferredStaticAvatar = setting.preferredStaticAvatar
|
UserDefaults.shared.preferredStaticAvatar = setting.preferredStaticAvatar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set emoji mode
|
||||||
|
if UserDefaults.shared.preferredStaticEmoji != setting.preferredStaticEmoji {
|
||||||
|
UserDefaults.shared.preferredStaticEmoji = setting.preferredStaticEmoji
|
||||||
|
}
|
||||||
|
|
||||||
// set browser
|
// set browser
|
||||||
if UserDefaults.shared.preferredUsingDefaultBrowser != setting.preferredUsingDefaultBrowser {
|
if UserDefaults.shared.preferredUsingDefaultBrowser != setting.preferredUsingDefaultBrowser {
|
||||||
UserDefaults.shared.preferredUsingDefaultBrowser = setting.preferredUsingDefaultBrowser
|
UserDefaults.shared.preferredUsingDefaultBrowser = setting.preferredUsingDefaultBrowser
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<AnyCancellable>()
|
|
||||||
|
|
||||||
let cache = NSCache<Key, ParseResultWrapper>()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -38,7 +38,6 @@ class AppContext: ObservableObject {
|
||||||
|
|
||||||
let placeholderImageCacheService = PlaceholderImageCacheService()
|
let placeholderImageCacheService = PlaceholderImageCacheService()
|
||||||
let blurhashImageCacheService = BlurhashImageCacheService()
|
let blurhashImageCacheService = BlurhashImageCacheService()
|
||||||
let statusContentCacheService = StatusContentCacheService()
|
|
||||||
|
|
||||||
let documentStore: DocumentStore
|
let documentStore: DocumentStore
|
||||||
private var documentStoreSubscription: AnyCancellable!
|
private var documentStoreSubscription: AnyCancellable!
|
||||||
|
|
Loading…
Reference in New Issue