diff --git a/ShareExtension/Info.plist b/ShareExtension/Info.plist
index 7b8ce5bd..3390cf12 100644
--- a/ShareExtension/Info.plist
+++ b/ShareExtension/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.4
+ 1.2.6
CFBundleVersion
- 74
+ 77
NSExtension
NSExtensionAttributes
diff --git a/TwidereSDK/Package.swift b/TwidereSDK/Package.swift
index ce2c827f..0699f50a 100644
--- a/TwidereSDK/Package.swift
+++ b/TwidereSDK/Package.swift
@@ -31,11 +31,11 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio.git", from: "2.34.0"),
.package(url: "https://github.com/Flipboard/FLAnimatedImage.git", from: "1.0.0"),
.package(url: "https://github.com/MainasuK/CommonOSLog", from: "0.1.1"),
- .package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("3.2.1")),
- .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.0"),
+ .package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("3.3.1")),
+ .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.5.0"),
.package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.1.0"),
.package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.1.1"),
- .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.12.1"),
+ .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.12.0"),
.package(url: "https://github.com/MainasuK/UITextView-Placeholder.git", from: "1.4.1"),
.package(url: "https://github.com/TimOliver/TOCropViewController.git", from: "2.6.0"),
.package(url: "https://github.com/MainasuK/KeyboardLayoutGuide.git", branch: "fix/iOS15"),
diff --git a/TwidereSDK/Sources/CoreDataStack/CoreDataStack.xcdatamodeld/CoreDataStack 4.xcdatamodel/contents b/TwidereSDK/Sources/CoreDataStack/CoreDataStack.xcdatamodeld/CoreDataStack 4.xcdatamodel/contents
index f282c668..deb57f40 100644
--- a/TwidereSDK/Sources/CoreDataStack/CoreDataStack.xcdatamodeld/CoreDataStack 4.xcdatamodel/contents
+++ b/TwidereSDK/Sources/CoreDataStack/CoreDataStack.xcdatamodeld/CoreDataStack 4.xcdatamodel/contents
@@ -1,5 +1,5 @@
-
+
@@ -85,7 +85,7 @@
-
+
@@ -179,6 +179,7 @@
+
@@ -243,7 +244,7 @@
-
+
\ No newline at end of file
diff --git a/TwidereSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonStatus.swift b/TwidereSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonStatus.swift
index f2fcfce6..7d723748 100644
--- a/TwidereSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonStatus.swift
+++ b/TwidereSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonStatus.swift
@@ -45,7 +45,7 @@ final public class MastodonStatus: NSManagedObject {
@NSManaged public private(set) var isMediaSensitive: Bool
// sourcery: autoUpdatableObject
- @NSManaged public private(set) var isContentReveal: Bool
+ @NSManaged public private(set) var isContentSensitiveToggled: Bool
// sourcery: autoUpdatableObject
@NSManaged public private(set) var isMediaSensitiveToggled: Bool
@@ -382,9 +382,9 @@ extension MastodonStatus: AutoUpdatableObject {
self.isMediaSensitive = isMediaSensitive
}
}
- public func update(isContentReveal: Bool) {
- if self.isContentReveal != isContentReveal {
- self.isContentReveal = isContentReveal
+ public func update(isContentSensitiveToggled: Bool) {
+ if self.isContentSensitiveToggled != isContentSensitiveToggled {
+ self.isContentSensitiveToggled = isContentSensitiveToggled
}
}
public func update(isMediaSensitiveToggled: Bool) {
diff --git a/TwidereSDK/Sources/CoreDataStack/Entity/Twitter/TwitterStatus.swift b/TwidereSDK/Sources/CoreDataStack/Entity/Twitter/TwitterStatus.swift
index c33f774d..4466c4a1 100644
--- a/TwidereSDK/Sources/CoreDataStack/Entity/Twitter/TwitterStatus.swift
+++ b/TwidereSDK/Sources/CoreDataStack/Entity/Twitter/TwitterStatus.swift
@@ -24,6 +24,11 @@ final public class TwitterStatus: NSManagedObject {
@NSManaged public private(set) var replyCount: Int64
// sourcery: autoUpdatableObject, autoGenerateProperty
@NSManaged public private(set) var repostCount: Int64
+ // sourcery: autoGenerateProperty
+ @NSManaged public private(set) var quoteCount: Int64
+
+ // Note: not mark `autoUpdatableObject` for `replyCount` and `quoteCount`
+ // to avoid V1 API update the exists value to 0
// sourcery: autoUpdatableObject, autoGenerateProperty
@NSManaged public private(set) var source: String?
@@ -191,6 +196,7 @@ extension TwitterStatus: AutoGenerateProperty {
public let likeCount: Int64
public let replyCount: Int64
public let repostCount: Int64
+ public let quoteCount: Int64
public let source: String?
public let replyToStatusID: TwitterStatus.ID?
public let replyToUserID: TwitterUser.ID?
@@ -203,6 +209,7 @@ extension TwitterStatus: AutoGenerateProperty {
likeCount: Int64,
replyCount: Int64,
repostCount: Int64,
+ quoteCount: Int64,
source: String?,
replyToStatusID: TwitterStatus.ID?,
replyToUserID: TwitterUser.ID?,
@@ -214,6 +221,7 @@ extension TwitterStatus: AutoGenerateProperty {
self.likeCount = likeCount
self.replyCount = replyCount
self.repostCount = repostCount
+ self.quoteCount = quoteCount
self.source = source
self.replyToStatusID = replyToStatusID
self.replyToUserID = replyToUserID
@@ -228,6 +236,7 @@ extension TwitterStatus: AutoGenerateProperty {
self.likeCount = property.likeCount
self.replyCount = property.replyCount
self.repostCount = property.repostCount
+ self.quoteCount = property.quoteCount
self.source = property.source
self.replyToStatusID = property.replyToStatusID
self.replyToUserID = property.replyToUserID
@@ -351,6 +360,18 @@ extension TwitterStatus: AutoUpdatableObject {
}
// sourcery:end
+ public func update(replyCount: Int64) {
+ if self.replyCount != replyCount {
+ self.replyCount = replyCount
+ }
+ }
+
+ public func update(quoteCount: Int64) {
+ if self.quoteCount != quoteCount {
+ self.quoteCount = quoteCount
+ }
+ }
+
public func update(isRepost: Bool, by user: TwitterUser) {
if isRepost {
if !repostBy.contains(user) {
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.44.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.44.imageset/Contents.json
new file mode 100644
index 00000000..f5d69410
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.44.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "circle.mask.44.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.44.imageset/circle.mask.44.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.44.imageset/circle.mask.44.pdf
new file mode 100644
index 00000000..991cbdde
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.44.imageset/circle.mask.44.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.88.imageset/Contents.json
similarity index 100%
rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.imageset/Contents.json
rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.88.imageset/Contents.json
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.imageset/circle.mask.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.88.imageset/circle.mask.pdf
similarity index 100%
rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.imageset/circle.mask.pdf
rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mask.88.imageset/circle.mask.pdf
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/Contents.json
index 34f095f7..921b2a29 100644
--- a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/Contents.json
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "badge.mastodon.pdf",
+ "filename" : "circle.mastodon.pdf",
"idiom" : "universal"
}
],
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/badge.mastodon.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/badge.mastodon.pdf
deleted file mode 100644
index efd13f26..00000000
Binary files a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/badge.mastodon.pdf and /dev/null differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/circle.mastodon.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/circle.mastodon.pdf
new file mode 100644
index 00000000..5e999e61
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.mastodon.imageset/circle.mastodon.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/Contents.json
index 894120fd..04f0cfc2 100644
--- a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/Contents.json
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "badge.twitter.pdf",
+ "filename" : "circle.twitter.pdf",
"idiom" : "universal"
}
],
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/badge.twitter.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/badge.twitter.pdf
deleted file mode 100644
index ec0cda3f..00000000
Binary files a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/badge.twitter.pdf and /dev/null differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/circle.twitter.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/circle.twitter.pdf
new file mode 100644
index 00000000..b1afc4be
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/circle.twitter.imageset/circle.twitter.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.imageset/robot.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.imageset/robot.pdf
index 80604d57..8c4f2e45 100644
Binary files a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.imageset/robot.pdf and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.imageset/robot.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.44.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.44.imageset/Contents.json
new file mode 100644
index 00000000..5890f57b
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.44.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "robot.mask.44.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.44.imageset/robot.mask.44.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.44.imageset/robot.mask.44.pdf
new file mode 100644
index 00000000..9660ddd0
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.44.imageset/robot.mask.44.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.88.imageset/Contents.json
similarity index 100%
rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.imageset/Contents.json
rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.88.imageset/Contents.json
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.imageset/robot.mask.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.88.imageset/robot.mask.pdf
similarity index 100%
rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.imageset/robot.mask.pdf
rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/robot.mask.88.imageset/robot.mask.pdf
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.imageset/verified.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.imageset/verified.pdf
index a644e0b3..5fc1201d 100644
Binary files a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.imageset/verified.pdf and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.imageset/verified.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.44.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.44.imageset/Contents.json
new file mode 100644
index 00000000..1dcd4299
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.44.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "verified.mask.44.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.44.imageset/verified.mask.44.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.44.imageset/verified.mask.44.pdf
new file mode 100644
index 00000000..67c26dd4
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.44.imageset/verified.mask.44.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.88.imageset/Contents.json
similarity index 100%
rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.imageset/Contents.json
rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.88.imageset/Contents.json
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.imageset/verified.mask.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.88.imageset/verified.mask.pdf
similarity index 100%
rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.imageset/verified.mask.pdf
rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Badge/verified.mask.88.imageset/verified.mask.pdf
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Human/person.large.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Human/person.large.imageset/Contents.json
new file mode 100644
index 00000000..73e91fda
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Human/person.large.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "person.large.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Human/person.large.imageset/person.large.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Human/person.large.imageset/person.large.pdf
new file mode 100644
index 00000000..86949eae
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Human/person.large.imageset/person.large.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.large.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.large.imageset/Contents.json
new file mode 100644
index 00000000..771d600b
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.large.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "bell.large.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.large.imageset/bell.large.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.large.imageset/bell.large.pdf
new file mode 100644
index 00000000..55e909ab
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.large.imageset/bell.large.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.ringing.large.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.ringing.large.imageset/Contents.json
new file mode 100644
index 00000000..577fceb6
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.ringing.large.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "bell.ringing.large.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.ringing.large.imageset/bell.ringing.large.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.ringing.large.imageset/bell.ringing.large.pdf
new file mode 100644
index 00000000..9604d69a
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/bell.ringing.large.imageset/bell.ringing.large.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/house.large.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/house.large.imageset/Contents.json
new file mode 100644
index 00000000..d5ca57a0
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/house.large.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "home.large.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/house.large.imageset/home.large.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/house.large.imageset/home.large.pdf
new file mode 100644
index 00000000..53dbbe2d
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/house.large.imageset/home.large.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/magnifyingglass.large.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/magnifyingglass.large.imageset/Contents.json
new file mode 100644
index 00000000..213ca8d4
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/magnifyingglass.large.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "magnifyingglass.large.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/magnifyingglass.large.imageset/magnifyingglass.large.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/magnifyingglass.large.imageset/magnifyingglass.large.pdf
new file mode 100644
index 00000000..76f6c5d0
Binary files /dev/null and b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Object&Tools/magnifyingglass.large.imageset/magnifyingglass.large.pdf differ
diff --git a/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift b/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift
index dc3ba27e..ff091473 100644
--- a/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift
+++ b/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift
@@ -35,13 +35,16 @@ public enum Asset {
public static let trendingUp = ImageAsset(name: "Arrows/trending.up")
}
public enum Badge {
- public static let circleMask = ImageAsset(name: "Badge/circle.mask")
+ public static let circleMask44 = ImageAsset(name: "Badge/circle.mask.44")
+ public static let circleMask88 = ImageAsset(name: "Badge/circle.mask.88")
public static let circleMastodon = ImageAsset(name: "Badge/circle.mastodon")
public static let circleTwitter = ImageAsset(name: "Badge/circle.twitter")
public static let robot = ImageAsset(name: "Badge/robot")
- public static let robotMask = ImageAsset(name: "Badge/robot.mask")
+ public static let robotMask44 = ImageAsset(name: "Badge/robot.mask.44")
+ public static let robotMask88 = ImageAsset(name: "Badge/robot.mask.88")
public static let verified = ImageAsset(name: "Badge/verified")
- public static let verifiedMask = ImageAsset(name: "Badge/verified.mask")
+ public static let verifiedMask44 = ImageAsset(name: "Badge/verified.mask.44")
+ public static let verifiedMask88 = ImageAsset(name: "Badge/verified.mask.88")
}
public enum Colors {
public enum Banner {
@@ -105,6 +108,7 @@ public enum Asset {
public static let person2 = ImageAsset(name: "Human/person.2")
public static let personExclamationMini = ImageAsset(name: "Human/person.exclamation.mini")
public static let person = ImageAsset(name: "Human/person")
+ public static let personLarge = ImageAsset(name: "Human/person.large")
public static let personMini = ImageAsset(name: "Human/person.mini")
public static let personPlusMini = ImageAsset(name: "Human/person.plus.mini")
}
@@ -138,7 +142,9 @@ public enum Asset {
}
public enum ObjectTools {
public static let bell = ImageAsset(name: "Object&Tools/bell")
+ public static let bellLarge = ImageAsset(name: "Object&Tools/bell.large")
public static let bellRinging = ImageAsset(name: "Object&Tools/bell.ringing")
+ public static let bellRingingLarge = ImageAsset(name: "Object&Tools/bell.ringing.large")
public static let blockedBadge = ImageAsset(name: "Object&Tools/blocked.badge")
public static let bookmarks = ImageAsset(name: "Object&Tools/bookmarks")
public static let camera = ImageAsset(name: "Object&Tools/camera")
@@ -149,6 +155,7 @@ public enum Asset {
public static let globeMini = ImageAsset(name: "Object&Tools/globe.mini")
public static let globeMiniInline = ImageAsset(name: "Object&Tools/globe.mini.inline")
public static let house = ImageAsset(name: "Object&Tools/house")
+ public static let houseLarge = ImageAsset(name: "Object&Tools/house.large")
public static let icRoundRefresh = ImageAsset(name: "Object&Tools/ic.round.refresh")
public static let lock = ImageAsset(name: "Object&Tools/lock")
public static let lockMini = ImageAsset(name: "Object&Tools/lock.mini")
@@ -156,6 +163,7 @@ public enum Asset {
public static let lockOpen = ImageAsset(name: "Object&Tools/lock.open")
public static let lockOpenMiniInline = ImageAsset(name: "Object&Tools/lock.open.mini.inline")
public static let magnifyingglass = ImageAsset(name: "Object&Tools/magnifyingglass")
+ public static let magnifyingglassLarge = ImageAsset(name: "Object&Tools/magnifyingglass.large")
public static let mappin = ImageAsset(name: "Object&Tools/mappin")
public static let mappinMini = ImageAsset(name: "Object&Tools/mappin.mini")
public static let note = ImageAsset(name: "Object&Tools/note")
diff --git a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/Cell/Input/ComposeInputTableViewCell.swift b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/Cell/Input/ComposeInputTableViewCell.swift
index 67a21e9e..36bb38aa 100644
--- a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/Cell/Input/ComposeInputTableViewCell.swift
+++ b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/Cell/Input/ComposeInputTableViewCell.swift
@@ -35,7 +35,7 @@ final public class ComposeInputTableViewCell: UITableViewCell {
public let avatarView: ProfileAvatarView = {
let imageView = ProfileAvatarView()
- imageView.dimension = ComposeInputTableViewCell.avatarImageViewSize.width
+ imageView.setup(dimension: .inline)
return imageView
}()
diff --git a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewController.swift b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewController.swift
index 70068f92..478e85e7 100644
--- a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewController.swift
+++ b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewController.swift
@@ -283,7 +283,7 @@ extension ComposeContentViewController {
guard let self = self else { return nil }
guard isRequestLocation, let currentLocation = currentLocation else { return nil }
- guard let authenticationContext = self.viewModel.configurationContext.authenticationService.activeAuthenticationContext.value,
+ guard let authenticationContext = self.viewModel.configurationContext.authenticationService.activeAuthenticationContext,
case let .twitter(twitterAuthenticationContext) = authenticationContext
else { return nil }
diff --git a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel+Diffable.swift b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel+Diffable.swift
index 1dd84f3d..d2f2d84c 100644
--- a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel+Diffable.swift
+++ b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel+Diffable.swift
@@ -173,11 +173,7 @@ extension ComposeContentViewModel {
tableView: tableView,
viewModel: ComposeReplyTableViewCell.ViewModel(
status: status,
- statusViewConfigureContext: StatusView.ConfigurationContext(
- dateTimeProvider: configurationContext.dateTimeProvider,
- twitterTextProvider: configurationContext.twitterTextProvider,
- activeAuthenticationContext: Just(nil).eraseToAnyPublisher()
- )
+ statusViewConfigureContext: configurationContext.statusViewConfigureContext
)
)
return composeReplyTableViewCell
diff --git a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel.swift b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel.swift
index 1ee377cf..38fae84e 100644
--- a/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel.swift
+++ b/TwidereSDK/Sources/TwidereComposeUI/ComposeContent/ComposeContentViewModel.swift
@@ -14,6 +14,7 @@ import CoreDataStack
import TwitterSDK
import MastodonSDK
import TwidereCore
+import TwidereUI
import TwidereAsset
import MetaTextKit
import TwitterMeta
@@ -207,7 +208,8 @@ public final class ComposeContentViewModel: NSObject {
guard let author = author else { return }
switch author {
case .twitter:
- let parseResult = configurationContext.twitterTextProvider.parse(text: currentTextInput)
+ let twitterTextProvider = configurationContext.statusViewConfigureContext.twitterTextProvider
+ let parseResult = twitterTextProvider.parse(text: currentTextInput)
self.textInputLimitProgress = {
guard parseResult.maxWeightedLength > 0 else { return .zero }
return CGFloat(parseResult.weightedLength) / CGFloat(parseResult.maxWeightedLength)
@@ -606,21 +608,18 @@ extension ComposeContentViewModel {
public let apiService: APIService
public let authenticationService: AuthenticationService
public let mastodonEmojiService: MastodonEmojiService
- public let dateTimeProvider: DateTimeProvider
- public let twitterTextProvider: TwitterTextProvider
-
+ public let statusViewConfigureContext: StatusView.ConfigurationContext
+
public init(
apiService: APIService,
authenticationService: AuthenticationService,
mastodonEmojiService: MastodonEmojiService,
- dateTimeProvider: DateTimeProvider,
- twitterTextProvider: TwitterTextProvider
+ statusViewConfigureContext: StatusView.ConfigurationContext
) {
self.apiService = apiService
self.authenticationService = authenticationService
self.mastodonEmojiService = mastodonEmojiService
- self.dateTimeProvider = dateTimeProvider
- self.twitterTextProvider = twitterTextProvider
+ self.statusViewConfigureContext = statusViewConfigureContext
}
}
}
@@ -653,7 +652,7 @@ extension ComposeContentViewModel {
let metaContent = TwitterMetaContent.convert(
content: content,
urlMaximumLength: .max,
- twitterTextProvider: configurationContext.twitterTextProvider
+ twitterTextProvider: configurationContext.statusViewConfigureContext.twitterTextProvider
)
return metaContent
diff --git a/TwidereSDK/Sources/TwidereComposeUI/MentionPick/MentionPickViewModel.swift b/TwidereSDK/Sources/TwidereComposeUI/MentionPick/MentionPickViewModel.swift
index 179bd25c..fd4414b3 100644
--- a/TwidereSDK/Sources/TwidereComposeUI/MentionPick/MentionPickViewModel.swift
+++ b/TwidereSDK/Sources/TwidereComposeUI/MentionPick/MentionPickViewModel.swift
@@ -37,7 +37,7 @@ public final class MentionPickViewModel {
self.primaryItem = primaryItem
self.secondaryItems = secondaryItems
- authenticationService.activeAuthenticationContext
+ authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
switch authenticationContext {
diff --git a/TwidereSDK/Sources/TwidereCore/Extension/CoreDataStack/TwitterAuthentication.swift b/TwidereSDK/Sources/TwidereCore/Extension/CoreDataStack/TwitterAuthentication.swift
index 74c4204d..f6e1a1b1 100644
--- a/TwidereSDK/Sources/TwidereCore/Extension/CoreDataStack/TwitterAuthentication.swift
+++ b/TwidereSDK/Sources/TwidereCore/Extension/CoreDataStack/TwitterAuthentication.swift
@@ -12,6 +12,7 @@ import TwidereCommon
import TwitterSDK
extension AuthenticationIndex {
+
public var user: UserObject? {
switch platform {
case .twitter:
diff --git a/TwidereSDK/Sources/TwidereCore/Model/Status/StatusObject.swift b/TwidereSDK/Sources/TwidereCore/Model/Status/StatusObject.swift
index 75436799..9d18b270 100644
--- a/TwidereSDK/Sources/TwidereCore/Model/Status/StatusObject.swift
+++ b/TwidereSDK/Sources/TwidereCore/Model/Status/StatusObject.swift
@@ -55,6 +55,7 @@ extension StatusObject {
public var attachments: [AttachmentObject] {
switch self {
case .twitter(let status):
+ let status = status.repost ?? status
return status.attachments.map { .twitter($0) }
case .mastodon(let status):
return status.attachments.map { .mastodon($0) }
diff --git a/TwidereSDK/Sources/TwidereCore/Model/Status/StatusVisibility.swift b/TwidereSDK/Sources/TwidereCore/Model/Status/StatusVisibility.swift
index b00d1dd0..a09fd54b 100644
--- a/TwidereSDK/Sources/TwidereCore/Model/Status/StatusVisibility.swift
+++ b/TwidereSDK/Sources/TwidereCore/Model/Status/StatusVisibility.swift
@@ -8,6 +8,7 @@
import UIKit
import MastodonSDK
import TwidereAsset
+import TwidereLocalization
public enum StatusVisibility {
case mastodon(Mastodon.Entity.Status.Visibility)
@@ -32,3 +33,23 @@ extension StatusVisibility {
}
}
}
+
+extension StatusVisibility {
+ public var accessibilityLabel: String? {
+ switch self {
+ case .mastodon(let visibility):
+ switch visibility {
+ case .public:
+ return L10n.Scene.Compose.Visibility.public
+ case .unlisted:
+ return L10n.Scene.Compose.Visibility.unlisted
+ case .private:
+ return L10n.Scene.Compose.Visibility.private
+ case .direct:
+ return L10n.Scene.Compose.Visibility.direct
+ case ._other:
+ return nil
+ }
+ }
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereCore/Persistence/Extension/TwitterStatus+Property.swift b/TwidereSDK/Sources/TwidereCore/Persistence/Extension/TwitterStatus+Property.swift
index 5a2d87c8..cc02622c 100644
--- a/TwidereSDK/Sources/TwidereCore/Persistence/Extension/TwitterStatus+Property.swift
+++ b/TwidereSDK/Sources/TwidereCore/Persistence/Extension/TwitterStatus+Property.swift
@@ -23,6 +23,7 @@ extension TwitterStatus.Property {
likeCount: entity.favoriteCount.flatMap(Int64.init) ?? 0,
replyCount: 0,
repostCount: entity.retweetCount.flatMap(Int64.init) ?? 0,
+ quoteCount: 0,
source: entity.source.flatMap { source in
do {
let document = try HTMLDocument(string: source)
@@ -128,6 +129,7 @@ extension TwitterStatus.Property {
likeCount: status.publicMetrics.flatMap { Int64($0.likeCount) } ?? 0,
replyCount: status.publicMetrics.flatMap { Int64($0.replyCount) } ?? 0,
repostCount: status.publicMetrics.flatMap { Int64($0.retweetCount) } ?? 0,
+ quoteCount: status.publicMetrics.flatMap { Int64($0.quoteCount) } ?? 0,
source: status.source,
replyToStatusID: status.repliedToID,
replyToUserID: status.inReplyToUserID,
diff --git a/TwidereSDK/Sources/TwidereCore/Persistence/Persistence+TwitterStatus+V2.swift b/TwidereSDK/Sources/TwidereCore/Persistence/Persistence+TwitterStatus+V2.swift
index c88fdf61..1cead545 100644
--- a/TwidereSDK/Sources/TwidereCore/Persistence/Persistence+TwitterStatus+V2.swift
+++ b/TwidereSDK/Sources/TwidereCore/Persistence/Persistence+TwitterStatus+V2.swift
@@ -226,12 +226,23 @@ extension Persistence.TwitterStatus {
) {
status.update(entities: TwitterEntity(entity: context.entity.status.entities))
+ // V2 only properties
+ // conversationID
context.entity.status.conversationID.flatMap { status.update(conversationID: $0) }
+ // replyCount, quoteCount
+ context.entity.status.publicMetrics.flatMap { metrics in
+ status.update(replyCount: Int64(metrics.replyCount))
+ status.update(replyCount: Int64(metrics.quoteCount))
+ }
+
+ // Not stable fields
+ // media
context.dictionary.media(for: context.entity.status)
.flatMap { media in
let attachments = media.compactMap { $0.twitterAttachment }
status.update(attachments: attachments)
}
+ // place
context.dictionary.place(for: context.entity.status)
.flatMap { place in
status.update(location: place.twitterLocation)
diff --git a/TwidereSDK/Sources/TwidereCore/Service/APIService/APIService+Status+Poll.swift b/TwidereSDK/Sources/TwidereCore/Service/APIService/APIService+Status+Poll.swift
index 9c8676f2..6f87c7ff 100644
--- a/TwidereSDK/Sources/TwidereCore/Service/APIService/APIService+Status+Poll.swift
+++ b/TwidereSDK/Sources/TwidereCore/Service/APIService/APIService+Status+Poll.swift
@@ -72,7 +72,7 @@ extension APIService {
private struct MastodonVotePollContext {
let pollID: Mastodon.Entity.Poll.ID
- let choices: [Int]
+ // let choices: [Int]
}
public func voteMastodonStatusPoll(
@@ -92,8 +92,7 @@ extension APIService {
}
let context = MastodonVotePollContext(
- pollID: poll.id,
- choices: choices
+ pollID: poll.id
)
return context
diff --git a/TwidereSDK/Sources/TwidereCore/Service/AuthenticationService.swift b/TwidereSDK/Sources/TwidereCore/Service/AuthenticationService.swift
index 2cc2f3bc..bb1278bb 100644
--- a/TwidereSDK/Sources/TwidereCore/Service/AuthenticationService.swift
+++ b/TwidereSDK/Sources/TwidereCore/Service/AuthenticationService.swift
@@ -29,7 +29,7 @@ public class AuthenticationService: NSObject {
public let authenticationIndexes = CurrentValueSubject<[AuthenticationIndex], Never>([])
public let activeAuthenticationIndex = CurrentValueSubject(nil)
- public let activeAuthenticationContext = CurrentValueSubject(nil)
+ @Published public var activeAuthenticationContext: AuthenticationContext? = nil
@available(*, deprecated, message: "")
public let activeTwitterAuthenticationBox = CurrentValueSubject(nil)
@@ -108,8 +108,7 @@ public class AuthenticationService: NSObject {
guard let authenticationContext = AuthenticationContext(authenticationIndex: authenticationIndex, appSecret: appSecret) else { return nil }
return authenticationContext
}
- .assign(to: \.value, on: activeAuthenticationContext)
- .store(in: &disposeBag)
+ .assign(to: &$activeAuthenticationContext)
do {
try authenticationIndexFetchedResultsController.performFetch()
diff --git a/TwidereSDK/Sources/TwidereCore/Service/ThemeService.swift b/TwidereSDK/Sources/TwidereCore/Service/ThemeService.swift
index c07f9226..d2027e72 100644
--- a/TwidereSDK/Sources/TwidereCore/Service/ThemeService.swift
+++ b/TwidereSDK/Sources/TwidereCore/Service/ThemeService.swift
@@ -39,11 +39,14 @@ public final class ThemeService {
// set tab bar appearance
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithDefaultBackground()
- let tabBarItemAppearance = UITabBarItemAppearance()
-// tabBarItemAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.clear]
-// tabBarItemAppearance.focused.titleTextAttributes = [.foregroundColor: UIColor.clear]
-// tabBarItemAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.clear]
-// tabBarItemAppearance.disabled.titleTextAttributes = [.foregroundColor: UIColor.clear]
+ tabBarAppearance.stackedLayoutAppearance = {
+ let tabBarItemAppearance = UITabBarItemAppearance()
+ tabBarItemAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.clear]
+ tabBarItemAppearance.focused.titleTextAttributes = [.foregroundColor: UIColor.clear]
+ tabBarItemAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.clear]
+ tabBarItemAppearance.disabled.titleTextAttributes = [.foregroundColor: UIColor.clear]
+ return tabBarItemAppearance
+ }()
UITabBar.appearance().standardAppearance = tabBarAppearance
UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance
// UITabBar.appearance().barTintColor = theme.tabBarBackgroundColor
diff --git a/TwidereSDK/Sources/TwidereLocalization/Generated/Strings.swift b/TwidereSDK/Sources/TwidereLocalization/Generated/Strings.swift
index acb89357..3ee35a4d 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Generated/Strings.swift
+++ b/TwidereSDK/Sources/TwidereLocalization/Generated/Strings.swift
@@ -36,19 +36,39 @@ public enum L10n {
public static let twitter = L10n.tr("Localizable", "Accessibility.Common.Logo.Twitter")
}
public enum Status {
+ /// Author avatar
+ public static let authorAvatar = L10n.tr("Localizable", "Accessibility.Common.Status.AuthorAvatar")
+ /// Boosted
+ public static let boosted = L10n.tr("Localizable", "Accessibility.Common.Status.Boosted")
+ /// Content Warning
+ public static let contentWarning = L10n.tr("Localizable", "Accessibility.Common.Status.ContentWarning")
+ /// Liked
+ public static let liked = L10n.tr("Localizable", "Accessibility.Common.Status.Liked")
/// Location
public static let location = L10n.tr("Localizable", "Accessibility.Common.Status.Location")
/// Media
public static let media = L10n.tr("Localizable", "Accessibility.Common.Status.Media")
+ /// Poll option
+ public static let pollOptionOrdinalPrefix = L10n.tr("Localizable", "Accessibility.Common.Status.PollOptionOrdinalPrefix")
/// Retweeted
public static let retweeted = L10n.tr("Localizable", "Accessibility.Common.Status.Retweeted")
public enum Actions {
+ /// Hide content
+ public static let hideContent = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.HideContent")
+ /// Hide media
+ public static let hideMedia = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.HideMedia")
/// Like
public static let like = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.Like")
+ /// Menu
+ public static let menu = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.Menu")
/// Reply
public static let reply = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.Reply")
/// Retweet
public static let retweet = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.Retweet")
+ /// Reveal content
+ public static let revealContent = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.RevealContent")
+ /// Reveal media
+ public static let revealMedia = L10n.tr("Localizable", "Accessibility.Common.Status.Actions.RevealMedia")
}
}
public enum Video {
@@ -104,6 +124,10 @@ public enum L10n {
public enum ManageAccounts {
/// Add
public static let add = L10n.tr("Localizable", "Accessibility.Scene.ManageAccounts.Add")
+ /// Current sign-in user: %@
+ public static func currentSignInUser(_ p1: Any) -> String {
+ return L10n.tr("Localizable", "Accessibility.Scene.ManageAccounts.CurrentSignInUser", String(describing: p1))
+ }
}
public enum Search {
/// History
@@ -117,6 +141,12 @@ public enum L10n {
public static let fontSize = L10n.tr("Localizable", "Accessibility.Scene.Settings.Display.FontSize")
}
}
+ public enum SignIn {
+ /// Please enter Mastodon domain to sign-in
+ public static let pleaseEnterMastodonDomainToSignIn = L10n.tr("Localizable", "Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn")
+ /// Twitter client authentication key setting
+ public static let twitterClientAuthenticationKeySetting = L10n.tr("Localizable", "Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting")
+ }
public enum Timeline {
/// Load
public static let loadGap = L10n.tr("Localizable", "Accessibility.Scene.Timeline.LoadGap")
@@ -136,6 +166,16 @@ public enum L10n {
}
}
}
+ public enum VoiceOver {
+ /// Double tap and hold to display menu
+ public static let doubleTapAndHoldToDisplayMenu = L10n.tr("Localizable", "Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu")
+ /// Double tap and hold to open the accounts panel
+ public static let doubleTapAndHoldToOpenTheAccountsPanel = L10n.tr("Localizable", "Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel")
+ /// Double tap to open profile
+ public static let doubleTapToOpenProfile = L10n.tr("Localizable", "Accessibility.VoiceOver.DoubleTapToOpenProfile")
+ /// Selected
+ public static let selected = L10n.tr("Localizable", "Accessibility.VoiceOver.Selected")
+ }
}
public enum Common {
@@ -574,6 +614,8 @@ public enum L10n {
public enum Actions {
/// Bookmark
public static let bookmark = L10n.tr("Localizable", "Common.Controls.Status.Actions.Bookmark")
+ /// Boost
+ public static let boost = L10n.tr("Localizable", "Common.Controls.Status.Actions.Boost")
/// Copy link
public static let copyLink = L10n.tr("Localizable", "Common.Controls.Status.Actions.CopyLink")
/// Copy text
@@ -588,6 +630,8 @@ public enum L10n {
public static let retweet = L10n.tr("Localizable", "Common.Controls.Status.Actions.Retweet")
/// Share
public static let share = L10n.tr("Localizable", "Common.Controls.Status.Actions.Share")
+ /// Share content
+ public static let shareContent = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShareContent")
/// Share link
public static let shareLink = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShareLink")
/// Translate
@@ -1395,14 +1439,52 @@ public enum L10n {
}
public enum Count {
+ /// Plural format key: "Input limit remains %#@character_count@"
+ public static func inputLimitRemains(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.input_limit_remains", p1)
+ }
+ /// Plural format key: "%#@like_count@"
+ public static func like(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.like", p1)
+ }
+ /// Plural format key: "%#@count_media@"
+ public static func media(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.media", p1)
+ }
/// Plural format key: "%#@count_people@"
public static func people(_ p1: Int) -> String {
return L10n.tr("Localizable", "count.people", p1)
}
+ /// Plural format key: "%#@post_count@"
+ public static func post(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.post", p1)
+ }
+ /// Plural format key: "%#@quote_count@"
+ public static func quote(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.quote", p1)
+ }
+ /// Plural format key: "%#@reblog_count@"
+ public static func reblog(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.reblog", p1)
+ }
+ /// Plural format key: "%#@reply_count@"
+ public static func reply(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.reply", p1)
+ }
+ /// Plural format key: "%#@retweet_count@"
+ public static func retweet(_ p1: Int) -> String {
+ return L10n.tr("Localizable", "count.retweet", p1)
+ }
/// Plural format key: "%#@count_vote@"
public static func vote(_ p1: Int) -> String {
return L10n.tr("Localizable", "count.vote", p1)
}
+ public enum MetricFormatted {
+ /// Plural format key: "%@ %#@post_count@"
+ public static func post(_ p1: Any, _ p2: Int) -> String {
+ return L10n.tr("Localizable", "count.metric_formatted.post", String(describing: p1), p2)
+ }
+ }
public enum People {
/// Plural format key: "%#@count_people_talking@"
public static func talking(_ p1: Int) -> String {
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.strings
index 5f157f4f..b3d6a65d 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "شعار تويتر";
"Accessibility.Common.More" = "المزيد";
"Accessibility.Common.NetworkImage" = "صورة الشبكة";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "أعجبني";
+"Accessibility.Common.Status.Actions.Menu" = "القائمة";
"Accessibility.Common.Status.Actions.Reply" = "رد";
"Accessibility.Common.Status.Actions.Retweet" = "أعد تغريده";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Author avatar";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "الموقع";
"Accessibility.Common.Status.Media" = "الوسائط";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "أُعيد تغريده";
"Accessibility.Common.Video.Play" = "شغّل الفيديو";
"Accessibility.Scene.Compose.AddMention" = "أُذكر مستخدمًا";
@@ -32,15 +42,22 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "القائمة المنزلقة للحساب";
"Accessibility.Scene.Home.Menu" = "القائمة";
"Accessibility.Scene.ManageAccounts.Add" = "أضف";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Current sign-in user: %@";
"Accessibility.Scene.Search.History" = "التأريخ";
"Accessibility.Scene.Search.Save" = "احفظ";
"Accessibility.Scene.Settings.Display.FontSize" = "حجم الخط";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Please enter Mastodon domain to sign-in";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter client authentication key setting";
"Accessibility.Scene.Timeline.LoadGap" = "حمّل";
"Accessibility.Scene.User.Location" = "الموقع";
"Accessibility.Scene.User.Tab.Favourite" = "المفضلة";
"Accessibility.Scene.User.Tab.Media" = "الوسائط";
"Accessibility.Scene.User.Tab.Status" = "الحالات";
"Accessibility.Scene.User.Website" = "الموقع الإلكتروني";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Double tap and hold to display menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Double tap and hold to open the accounts panel";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Double tap to open profile";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "تويتر يعلق الحسابات التي تخالف %@";
"Common.Alerts.AccountSuspended.Title" = "الحساب معلق";
"Common.Alerts.AccountSuspended.TwitterRules" = "قواعد تويتر";
@@ -144,7 +161,7 @@
"Common.Controls.Friendship.Actions.Block" = "احجب";
"Common.Controls.Friendship.Actions.Blocked" = "محجوب";
"Common.Controls.Friendship.Actions.Follow" = "تابعه";
-"Common.Controls.Friendship.Actions.Following" = "يتابع";
+"Common.Controls.Friendship.Actions.Following" = "المتابَعون";
"Common.Controls.Friendship.Actions.Mute" = "اكتم";
"Common.Controls.Friendship.Actions.Pending" = "معلق";
"Common.Controls.Friendship.Actions.Report" = "أبلغ";
@@ -161,11 +178,12 @@
"Common.Controls.Friendship.UserIsFollowingYou" = "%@ يتابعك";
"Common.Controls.Friendship.UserIsNotFollowingYou" = "%@ لا يتابعك";
"Common.Controls.Ios.PhotoLibrary" = "مكتبة الصور";
-"Common.Controls.List.NoResults" = "No results";
+"Common.Controls.List.NoResults" = "لا نتائج";
"Common.Controls.ProfileDashboard.Followers" = "المتابِعون";
-"Common.Controls.ProfileDashboard.Following" = "يتابع";
+"Common.Controls.ProfileDashboard.Following" = "المتابَعون";
"Common.Controls.ProfileDashboard.Listed" = "مدرج";
"Common.Controls.Status.Actions.Bookmark" = "أضفه للعلامات";
+"Common.Controls.Status.Actions.Boost" = "Boost";
"Common.Controls.Status.Actions.CopyLink" = "انسخ الرابط";
"Common.Controls.Status.Actions.CopyText" = "انسخ النص";
"Common.Controls.Status.Actions.DeleteTweet" = "احذف التغريدة";
@@ -173,6 +191,7 @@
"Common.Controls.Status.Actions.Quote" = "اقتبس";
"Common.Controls.Status.Actions.Retweet" = "أعد تغريده";
"Common.Controls.Status.Actions.Share" = "شاركه";
+"Common.Controls.Status.Actions.ShareContent" = "شارك المحتوى";
"Common.Controls.Status.Actions.ShareLink" = "شارك الرابط";
"Common.Controls.Status.Actions.Translate" = "ترجمه";
"Common.Controls.Status.Actions.UnpinFromProfile" = "ألغ تثبيتها من ملفك الشخصي";
@@ -250,7 +269,7 @@
"Scene.Compose.Vote.Expiration.6Hour" = "6 ساعات";
"Scene.Compose.Vote.Expiration.7Day" = "7 أيام";
"Scene.Compose.Vote.Multiple" = "خيارات متعددة";
-"Scene.Compose.Vote.PlaceholderIndex" = "Choice %d";
+"Scene.Compose.Vote.PlaceholderIndex" = "اختر %d";
"Scene.ComposeHashtagSearch.SearchPlaceholder" = "ابحث عن وسم";
"Scene.ComposeUserSearch.SearchPlaceholder" = "ابحث عن مستخدم";
"Scene.Drafts.Actions.DeleteDraft" = "احذف المسودة";
@@ -260,7 +279,7 @@
"Scene.Drawer.SignIn" = "لِج";
"Scene.Federated.Title" = "الشبكة الموحدة";
"Scene.Followers.Title" = "المتابِعون";
-"Scene.Following.Title" = "يتابع";
+"Scene.Following.Title" = "المتابَعون";
"Scene.Likes.Title" = "الإعجابات";
"Scene.Listed.Title" = "مدرج";
"Scene.Lists.Icons.Create" = "أنشئ قائمة";
@@ -364,7 +383,7 @@
"Scene.Settings.Display.SectionHeader.Media" = "الوسائط";
"Scene.Settings.Display.SectionHeader.Preview" = "معاينة";
"Scene.Settings.Display.SectionHeader.Text" = "النص";
-"Scene.Settings.Display.Text.AvatarStyle" = "نمط الصورة الرمزية";
+"Scene.Settings.Display.Text.AvatarStyle" = "شكل الصورة الرمزية";
"Scene.Settings.Display.Text.Circle" = "دائرة";
"Scene.Settings.Display.Text.RoundedSquare" = "مربع مدور الزوايا";
"Scene.Settings.Display.Text.UseTheSystemFontSize" = "استخدم حجم خط النظام";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.stringsdict
index cada4ede..3de26827 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ar.lproj/Localizable.stringsdict
@@ -2,6 +2,222 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld media
+ one
+ 1 media
+ two
+ 1 وسائط
+ few
+ %ld media
+ many
+ %ld media
+ other
+ %ld media
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld characters
+ one
+ 1 character
+ two
+ %ld characters
+ few
+ %ld characters
+ many
+ %ld characters
+ other
+ %ld characters
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ posts
+ one
+ post
+ two
+ posts
+ few
+ posts
+ many
+ posts
+ other
+ posts
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld posts
+ one
+ 1 post
+ two
+ %ld posts
+ few
+ %ld posts
+ many
+ %ld posts
+ other
+ %ld posts
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld quotes
+ one
+ 1 quote
+ two
+ %ld quotes
+ few
+ %ld quotes
+ many
+ %ld quotes
+ other
+ %ld quotes
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld likes
+ one
+ 1 like
+ two
+ %ld likes
+ few
+ %ld likes
+ many
+ %ld likes
+ other
+ %ld likes
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld retweets
+ one
+ 1 retweet
+ two
+ %ld retweets
+ few
+ %ld retweets
+ many
+ %ld retweets
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld reblogs
+ one
+ 1 reblog
+ two
+ %ld reblogs
+ few
+ %ld reblogs
+ many
+ %ld reblogs
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ zero
+ %ld replies
+ one
+ 1 reply
+ two
+ %ld replies
+ few
+ %ld replies
+ many
+ %ld replies
+ other
+ %ld replies
+
+
count.vote
NSStringLocalizedFormatKey
@@ -15,15 +231,15 @@
zero
%ld votes
one
- 1 vote
+ صوتٌ واحِد
two
- %ld votes
+ %ld صوتين
few
- %ld votes
+ %ld أصوات
many
- %ld votes
+ %ld صَوْتاً
other
- %ld votes
+ %ld صوت
count.people
@@ -37,17 +253,17 @@
NSStringFormatValueTypeKey
ld
zero
- %ld people
+ %ld شخص
one
- 1 people
+ شخص واحد
two
- %ld people
+ شخصان %ld
few
- %ld people
+ %ld أشخاص
many
- %ld people
+ %ld شخصًا
other
- %ld people
+ %ld شخص
count.people.talking
@@ -63,15 +279,15 @@
zero
%ld اشخاص يتحدثون
one
- %ld اشخاص يتحدثون
+ شخصٌ واحدٌ يتحدَّث
two
- %ld اشخاص يتحدثون
+ %ld شخصان يتحدثان
few
%ld اشخاص يتحدثون
many
- %ld اشخاص يتحدثون
+ %ld شخص يتحدَّثون
other
- %ld اشخاص يتحدثون
+ %ld شخص يتحدث
date.year.left
@@ -87,13 +303,13 @@
zero
%ld سنوات متبقية
one
- %ld سنوات متبقية
+ تتبقى سنة
two
- %ld سنوات متبقية
+ %ld تتبقى سنتان
few
%ld سنوات متبقية
many
- %ld سنوات متبقية
+ %ld سنة متبقية
other
%ld سنوات متبقية
@@ -111,15 +327,15 @@
zero
%ld شهور متبقية
one
- %ld شهور متبقية
+ يتبقى شهر
two
- %ld شهور متبقية
+ %ld يتبقى شهران
few
%ld شهور متبقية
many
- %ld شهور متبقية
+ %ld شهر متبقية
other
- %ld شهور متبقية
+ %ld شهر متبقية
date.day.left
@@ -135,15 +351,15 @@
zero
%ld أيام متبقية
one
- %ld أيام متبقية
+ يتبقى يوم
two
- %ld أيام متبقية
+ %ld يتبقى يومان
few
%ld أيام متبقية
many
- %ld أيام متبقية
+ تبقى %ld يوم
other
- %ld أيام متبقية
+ تبقى %ld يوم
date.hour.left
@@ -159,15 +375,15 @@
zero
%ld ساعات متبقية
one
- %ld ساعات متبقية
+ تتبقى ساعة
two
- %ld ساعات متبقية
+ %ld تتبقى ساعتان
few
%ld ساعات متبقية
many
- %ld ساعات متبقية
+ %ld ساعة متبقية
other
- %ld ساعات متبقية
+ %ld ساعة متبقية
date.minute.left
@@ -183,15 +399,15 @@
zero
%ld دقيقية متبقية
one
- %ld دقيقة متبقية
+ تتبقى دقيقة
two
- %ld دقائق متبقية
+ %ld تتبقى دقيقتان
few
%ld دقائق متبقية
many
- %ld دقائق متبقية
+ %ld دقيقة متبقية
other
- %ld دقائق متبقية
+ %ld دقيقة متبقية
date.second.left
@@ -207,15 +423,15 @@
zero
%ld ثواني متبقية
one
- %ld ثانية متبقية
+ متبقي ثانية واحدة
two
- %ld ثواني متبقية
+ متبقي ثانيتان %ld
few
%ld ثواني متبقية
many
- %ld ثواني متبقية
+ %ld ثانية متبقية
other
- %ld ثواني متبقية
+ %ld ثانية متبقية
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.strings
index 6a75978f..a39e9c19 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "Logo de Twitter";
"Accessibility.Common.More" = "Més";
"Accessibility.Common.NetworkImage" = "Imatge de xarxa";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "M'agrada";
+"Accessibility.Common.Status.Actions.Menu" = "Menú";
"Accessibility.Common.Status.Actions.Reply" = "Resposta";
"Accessibility.Common.Status.Actions.Retweet" = "Repiular";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Author avatar";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "Ubicació";
"Accessibility.Common.Status.Media" = "Multimèdia";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "Repiulades";
"Accessibility.Common.Video.Play" = "Reprodueix el vídeo";
"Accessibility.Scene.Compose.AddMention" = "Afegeix menció";
@@ -32,15 +42,22 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "Desplegable del compte";
"Accessibility.Scene.Home.Menu" = "Menú";
"Accessibility.Scene.ManageAccounts.Add" = "Afegir";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Current sign-in user: %@";
"Accessibility.Scene.Search.History" = "Historial";
"Accessibility.Scene.Search.Save" = "Desa";
"Accessibility.Scene.Settings.Display.FontSize" = "Mida de la lletra";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Please enter Mastodon domain to sign-in";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter client authentication key setting";
"Accessibility.Scene.Timeline.LoadGap" = "Carrega";
"Accessibility.Scene.User.Location" = "Ubicació";
"Accessibility.Scene.User.Tab.Favourite" = "Favorit";
"Accessibility.Scene.User.Tab.Media" = "Multimèdia";
"Accessibility.Scene.User.Tab.Status" = "Estats";
"Accessibility.Scene.User.Website" = "Lloc web";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Double tap and hold to display menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Double tap and hold to open the accounts panel";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Double tap to open profile";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "Twitter suspèn comptes que violen %@";
"Common.Alerts.AccountSuspended.Title" = "Compte suspès";
"Common.Alerts.AccountSuspended.TwitterRules" = "Normes de Twitter";
@@ -166,6 +183,7 @@
"Common.Controls.ProfileDashboard.Following" = "Seguint";
"Common.Controls.ProfileDashboard.Listed" = "Llistat";
"Common.Controls.Status.Actions.Bookmark" = "Marcador";
+"Common.Controls.Status.Actions.Boost" = "Boost";
"Common.Controls.Status.Actions.CopyLink" = "Copia l'enllaç";
"Common.Controls.Status.Actions.CopyText" = "Copia el text";
"Common.Controls.Status.Actions.DeleteTweet" = "Esborra la piulada";
@@ -173,6 +191,7 @@
"Common.Controls.Status.Actions.Quote" = "Cita";
"Common.Controls.Status.Actions.Retweet" = "Repiular";
"Common.Controls.Status.Actions.Share" = "Share";
+"Common.Controls.Status.Actions.ShareContent" = "Share content";
"Common.Controls.Status.Actions.ShareLink" = "Comparteix l'enllaç";
"Common.Controls.Status.Actions.Translate" = "Translate";
"Common.Controls.Status.Actions.UnpinFromProfile" = "Unpin from Profile";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.stringsdict
index 6e26a2c3..e41b46c4 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ca.lproj/Localizable.stringsdict
@@ -2,6 +2,150 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 media
+ other
+ %ld media
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ post
+ other
+ posts
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 post
+ other
+ %ld posts
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 quote
+ other
+ %ld quotes
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 like
+ other
+ %ld likes
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 retweet
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reblog
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reply
+ other
+ %ld replies
+
+
count.vote
NSStringLocalizedFormatKey
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.strings
index f41c1beb..1bc3cc54 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "Twitter logo";
"Accessibility.Common.More" = "Mehr";
"Accessibility.Common.NetworkImage" = "Netzwerkbild";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "Gefällt mir";
+"Accessibility.Common.Status.Actions.Menu" = "Menü";
"Accessibility.Common.Status.Actions.Reply" = "Antworten";
"Accessibility.Common.Status.Actions.Retweet" = "Retweet";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Author avatar";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "Standort";
"Accessibility.Common.Status.Media" = "Medien";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "Retweetet";
"Accessibility.Common.Video.Play" = "Video abspielen";
"Accessibility.Scene.Compose.AddMention" = "Add mention";
@@ -32,15 +42,22 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "Konto DropDown-Liste";
"Accessibility.Scene.Home.Menu" = "Menü";
"Accessibility.Scene.ManageAccounts.Add" = "Hinzufügen";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Current sign-in user: %@";
"Accessibility.Scene.Search.History" = "Verlauf";
"Accessibility.Scene.Search.Save" = "Speichern";
"Accessibility.Scene.Settings.Display.FontSize" = "Schriftgröße";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Please enter Mastodon domain to sign-in";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter client authentication key setting";
"Accessibility.Scene.Timeline.LoadGap" = "Laden";
"Accessibility.Scene.User.Location" = "Standort";
"Accessibility.Scene.User.Tab.Favourite" = "Favoriten";
"Accessibility.Scene.User.Tab.Media" = "Medien";
"Accessibility.Scene.User.Tab.Status" = "Status";
"Accessibility.Scene.User.Website" = "Webseite";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Double tap and hold to display menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Double tap and hold to open the accounts panel";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Double tap to open profile";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "Twitter sperrt Konten die gegen %@ verstoßen";
"Common.Alerts.AccountSuspended.Title" = "Konto gesperrt";
"Common.Alerts.AccountSuspended.TwitterRules" = "Twitter-Regeln";
@@ -166,6 +183,7 @@
"Common.Controls.ProfileDashboard.Following" = "Folgen";
"Common.Controls.ProfileDashboard.Listed" = "Listet";
"Common.Controls.Status.Actions.Bookmark" = "Lesezeichen";
+"Common.Controls.Status.Actions.Boost" = "Boost";
"Common.Controls.Status.Actions.CopyLink" = "Link kopieren";
"Common.Controls.Status.Actions.CopyText" = "Text kopieren";
"Common.Controls.Status.Actions.DeleteTweet" = "Tweet löschen";
@@ -173,6 +191,7 @@
"Common.Controls.Status.Actions.Quote" = "Zitieren";
"Common.Controls.Status.Actions.Retweet" = "Retweet";
"Common.Controls.Status.Actions.Share" = "Share";
+"Common.Controls.Status.Actions.ShareContent" = "Share content";
"Common.Controls.Status.Actions.ShareLink" = "Link teilen";
"Common.Controls.Status.Actions.Translate" = "Translate";
"Common.Controls.Status.Actions.UnpinFromProfile" = "Unpin from Profile";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.stringsdict
index 6e26a2c3..e41b46c4 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/de.lproj/Localizable.stringsdict
@@ -2,6 +2,150 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 media
+ other
+ %ld media
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ post
+ other
+ posts
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 post
+ other
+ %ld posts
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 quote
+ other
+ %ld quotes
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 like
+ other
+ %ld likes
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 retweet
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reblog
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reply
+ other
+ %ld replies
+
+
count.vote
NSStringLocalizedFormatKey
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.strings
index e80aa655..0e323562 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "Twitter Logo";
"Accessibility.Common.More" = "More";
"Accessibility.Common.NetworkImage" = "Network Image";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "Like";
+"Accessibility.Common.Status.Actions.Menu" = "Menu";
"Accessibility.Common.Status.Actions.Reply" = "Reply";
"Accessibility.Common.Status.Actions.Retweet" = "Retweet";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Author avatar";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "Location";
"Accessibility.Common.Status.Media" = "Media";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "Retweeted";
"Accessibility.Common.Video.Play" = "Play video";
"Accessibility.Scene.Compose.AddMention" = "Add mention";
@@ -32,15 +42,22 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "Account DropDown";
"Accessibility.Scene.Home.Menu" = "Menu";
"Accessibility.Scene.ManageAccounts.Add" = "Add";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Current sign-in user: %@";
"Accessibility.Scene.Search.History" = "History";
"Accessibility.Scene.Search.Save" = "Save";
"Accessibility.Scene.Settings.Display.FontSize" = "Font Size";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Please enter Mastodon domain to sign-in";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter client authentication key setting";
"Accessibility.Scene.Timeline.LoadGap" = "Load";
"Accessibility.Scene.User.Location" = "Location";
"Accessibility.Scene.User.Tab.Favourite" = "Favourite";
"Accessibility.Scene.User.Tab.Media" = "Media";
"Accessibility.Scene.User.Tab.Status" = "Statuses";
"Accessibility.Scene.User.Website" = "Website";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Double tap and hold to display menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Double tap and hold to open the accounts panel";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Double tap to open profile";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "Twitter suspends accounts which violate the %@";
"Common.Alerts.AccountSuspended.Title" = "Account Suspended";
"Common.Alerts.AccountSuspended.TwitterRules" = "Twitter Rules";
@@ -166,6 +183,7 @@
"Common.Controls.ProfileDashboard.Following" = "Following";
"Common.Controls.ProfileDashboard.Listed" = "Listed";
"Common.Controls.Status.Actions.Bookmark" = "Bookmark";
+"Common.Controls.Status.Actions.Boost" = "Boost";
"Common.Controls.Status.Actions.CopyLink" = "Copy link";
"Common.Controls.Status.Actions.CopyText" = "Copy text";
"Common.Controls.Status.Actions.DeleteTweet" = "Delete tweet";
@@ -173,6 +191,7 @@
"Common.Controls.Status.Actions.Quote" = "Quote";
"Common.Controls.Status.Actions.Retweet" = "Retweet";
"Common.Controls.Status.Actions.Share" = "Share";
+"Common.Controls.Status.Actions.ShareContent" = "Share content";
"Common.Controls.Status.Actions.ShareLink" = "Share link";
"Common.Controls.Status.Actions.Translate" = "Translate";
"Common.Controls.Status.Actions.UnpinFromProfile" = "Unpin from Profile";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.stringsdict
index 6e26a2c3..e41b46c4 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/en.lproj/Localizable.stringsdict
@@ -2,6 +2,150 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 media
+ other
+ %ld media
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ post
+ other
+ posts
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 post
+ other
+ %ld posts
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 quote
+ other
+ %ld quotes
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 like
+ other
+ %ld likes
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 retweet
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reblog
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reply
+ other
+ %ld replies
+
+
count.vote
NSStringLocalizedFormatKey
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.strings
index 8b4cacf9..fe71b5aa 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "Logo de Twitter";
"Accessibility.Common.More" = "Más";
"Accessibility.Common.NetworkImage" = "Imagen de red";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "Me gusta";
+"Accessibility.Common.Status.Actions.Menu" = "Menú";
"Accessibility.Common.Status.Actions.Reply" = "Responder";
"Accessibility.Common.Status.Actions.Retweet" = "Retwittear";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Author avatar";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "Ubicación";
"Accessibility.Common.Status.Media" = "Multimedia";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "Retwitteados";
"Accessibility.Common.Video.Play" = "Reproducir vídeo";
"Accessibility.Scene.Compose.AddMention" = "Añadir mención";
@@ -32,15 +42,22 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "Desplegable de la cuenta";
"Accessibility.Scene.Home.Menu" = "Menú";
"Accessibility.Scene.ManageAccounts.Add" = "Añadir";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Current sign-in user: %@";
"Accessibility.Scene.Search.History" = "Historial";
"Accessibility.Scene.Search.Save" = "Guardar";
"Accessibility.Scene.Settings.Display.FontSize" = "Tamaño de fuente";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Please enter Mastodon domain to sign-in";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter client authentication key setting";
"Accessibility.Scene.Timeline.LoadGap" = "Cargar";
"Accessibility.Scene.User.Location" = "Ubicación";
"Accessibility.Scene.User.Tab.Favourite" = "Favorito";
"Accessibility.Scene.User.Tab.Media" = "Multimedia";
"Accessibility.Scene.User.Tab.Status" = "Estados";
"Accessibility.Scene.User.Website" = "Página web";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Double tap and hold to display menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Double tap and hold to open the accounts panel";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Double tap to open profile";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "Twitter suspende cuentas que violan %@";
"Common.Alerts.AccountSuspended.Title" = "Cuenta suspendida";
"Common.Alerts.AccountSuspended.TwitterRules" = "Reglas de Twitter";
@@ -87,7 +104,7 @@
"Common.Alerts.MediaSaveFail.Title" = "Error al guardar archivo multimedia";
"Common.Alerts.MediaSaved.Title" = "Archivo multimedia guardado";
"Common.Alerts.MediaSaving.Title" = "Guardando multimedia";
-"Common.Alerts.MediaSharing.Title" = "Los medios serán compartidos una vez completada la descarga";
+"Common.Alerts.MediaSharing.Title" = "Los archivos multimedia serán compartidos una vez completada la descarga";
"Common.Alerts.MuteUserConfirm.Title" = "¿Desea silenciar a %@?";
"Common.Alerts.MuteUserSuccess.Title" = "%@ ha sido silenciado";
"Common.Alerts.NoTweetsFound.Title" = "No se encontraron Tweets";
@@ -136,7 +153,7 @@
"Common.Controls.Actions.Remove" = "Eliminar";
"Common.Controls.Actions.Save" = "Guardar";
"Common.Controls.Actions.SavePhoto" = "Guardar foto";
-"Common.Controls.Actions.ShareMedia" = "Compartir medios";
+"Common.Controls.Actions.ShareMedia" = "Compartir multimedia";
"Common.Controls.Actions.SignIn" = "Iniciar sesión";
"Common.Controls.Actions.SignOut" = "Cerrar sesión";
"Common.Controls.Actions.TakePhoto" = "Hacer una foto";
@@ -161,11 +178,12 @@
"Common.Controls.Friendship.UserIsFollowingYou" = "%@ te sigue";
"Common.Controls.Friendship.UserIsNotFollowingYou" = "%@ no te sigue";
"Common.Controls.Ios.PhotoLibrary" = "Galería de fotos";
-"Common.Controls.List.NoResults" = "No results";
+"Common.Controls.List.NoResults" = "Sin resultados";
"Common.Controls.ProfileDashboard.Followers" = "Seguidores";
"Common.Controls.ProfileDashboard.Following" = "Siguiendo";
"Common.Controls.ProfileDashboard.Listed" = "Listado";
"Common.Controls.Status.Actions.Bookmark" = "Marcador";
+"Common.Controls.Status.Actions.Boost" = "Boost";
"Common.Controls.Status.Actions.CopyLink" = "Copiar enlace";
"Common.Controls.Status.Actions.CopyText" = "Copiar texto";
"Common.Controls.Status.Actions.DeleteTweet" = "Eliminar tweet";
@@ -173,6 +191,7 @@
"Common.Controls.Status.Actions.Quote" = "Cita";
"Common.Controls.Status.Actions.Retweet" = "Retwittear";
"Common.Controls.Status.Actions.Share" = "Compartir";
+"Common.Controls.Status.Actions.ShareContent" = "Compartir contenido";
"Common.Controls.Status.Actions.ShareLink" = "Compartir enlace";
"Common.Controls.Status.Actions.Translate" = "Traducir";
"Common.Controls.Status.Actions.UnpinFromProfile" = "Desfijar del perfil";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.stringsdict
index 95479f4a..b4800dbb 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/es.lproj/Localizable.stringsdict
@@ -2,6 +2,150 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 archivo multimedia
+ other
+ %ld archivos multimedia
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 character
+ other
+ %ld characters
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ post
+ other
+ posts
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 post
+ other
+ %ld posts
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 quote
+ other
+ %ld quotes
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 like
+ other
+ %ld likes
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 retweet
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reblog
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reply
+ other
+ %ld replies
+
+
count.vote
NSStringLocalizedFormatKey
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.strings
index 23fc3521..c0a36a57 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.strings
@@ -3,16 +3,26 @@
"Accessibility.Common.Done" = "完了";
"Accessibility.Common.Logo.Github" = "GitHub ロゴ";
"Accessibility.Common.Logo.Mastodon" = "Mastodon ロゴ";
-"Accessibility.Common.Logo.Telegram" = "Telegram Logo";
+"Accessibility.Common.Logo.Telegram" = "Telegram ロゴ";
"Accessibility.Common.Logo.Twidere" = "Twidere X ロゴ";
"Accessibility.Common.Logo.Twitter" = "Twitter ロゴ";
"Accessibility.Common.More" = "もっと見る";
-"Accessibility.Common.NetworkImage" = "Network Image";
+"Accessibility.Common.NetworkImage" = "ネットワーク画像";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "Like";
+"Accessibility.Common.Status.Actions.Menu" = "メニュー";
"Accessibility.Common.Status.Actions.Reply" = "返信";
-"Accessibility.Common.Status.Actions.Retweet" = "Retweet";
+"Accessibility.Common.Status.Actions.Retweet" = "リツイート";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Author avatar";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "位置情報";
"Accessibility.Common.Status.Media" = "メディア";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "Retweeted";
"Accessibility.Common.Video.Play" = "動画の再生";
"Accessibility.Scene.Compose.AddMention" = "Add mention";
@@ -32,31 +42,38 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "アカウント ドロップダウン";
"Accessibility.Scene.Home.Menu" = "メニュー";
"Accessibility.Scene.ManageAccounts.Add" = "追加";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Current sign-in user: %@";
"Accessibility.Scene.Search.History" = "履歴";
"Accessibility.Scene.Search.Save" = "保存";
"Accessibility.Scene.Settings.Display.FontSize" = "フォントサイズ";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Please enter Mastodon domain to sign-in";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter client authentication key setting";
"Accessibility.Scene.Timeline.LoadGap" = "読み込み";
"Accessibility.Scene.User.Location" = "位置情報";
"Accessibility.Scene.User.Tab.Favourite" = "お気に入り";
"Accessibility.Scene.User.Tab.Media" = "メディア";
"Accessibility.Scene.User.Tab.Status" = "投稿";
"Accessibility.Scene.User.Website" = "ウェブサイト";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Double tap and hold to display menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Double tap and hold to open the accounts panel";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Double tap to open profile";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "Twitterは%@に違反したアカウントを凍結します";
"Common.Alerts.AccountSuspended.Title" = "アカウントは凍結されています";
"Common.Alerts.AccountSuspended.TwitterRules" = "Twitterルール";
"Common.Alerts.AccountTemporarilyLocked.Message" = "Twitter を開いてロックを解除する";
"Common.Alerts.AccountTemporarilyLocked.Title" = "アカウントは一時的にロックされています";
-"Common.Alerts.BlockUserConfirm.Title" = "Do you want to block %@?";
+"Common.Alerts.BlockUserConfirm.Title" = "%@ をブロックしますか?";
"Common.Alerts.BlockUserSuccess.Title" = "%@ はブロックされました";
"Common.Alerts.CancelFollowRequest.Message" = "%@ のフォローリクエストをキャンセルしますか?";
-"Common.Alerts.DeleteTootConfirm.Message" = "Do you want to delete this toot?";
-"Common.Alerts.DeleteTootConfirm.Title" = "Delete Toot";
-"Common.Alerts.DeleteTweetConfirm.Message" = "Do you want to delete this tweet?";
-"Common.Alerts.DeleteTweetConfirm.Title" = "Delete Tweet";
+"Common.Alerts.DeleteTootConfirm.Message" = "このトゥートを削除しますか?";
+"Common.Alerts.DeleteTootConfirm.Title" = "トゥートを削除";
+"Common.Alerts.DeleteTweetConfirm.Message" = "このツイートを削除しますか?";
+"Common.Alerts.DeleteTweetConfirm.Title" = "ツイートを削除";
"Common.Alerts.FailedToBlockUser.Message" = "もう一度やり直してください";
"Common.Alerts.FailedToBlockUser.Title" = "%@ のブロックに失敗";
"Common.Alerts.FailedToDeleteToot.Message" = "もう一度やり直してください";
-"Common.Alerts.FailedToDeleteToot.Title" = "Failed to Delete Toot";
+"Common.Alerts.FailedToDeleteToot.Title" = "トゥートの削除に失敗しました";
"Common.Alerts.FailedToDeleteTweet.Message" = "もう一度やり直してください";
"Common.Alerts.FailedToDeleteTweet.Title" = "ツイートの削除に失敗しました";
"Common.Alerts.FailedToFollowing.Message" = "もう一度やり直してください";
@@ -87,8 +104,8 @@
"Common.Alerts.MediaSaveFail.Title" = "メディアを保存できませんでした";
"Common.Alerts.MediaSaved.Title" = "メディアを保存しました";
"Common.Alerts.MediaSaving.Title" = "メディアの保存";
-"Common.Alerts.MediaSharing.Title" = "Media will be shared after download is completed";
-"Common.Alerts.MuteUserConfirm.Title" = "Do you want to mute %@?";
+"Common.Alerts.MediaSharing.Title" = "メディアはダウンロードが完了したら共有されます";
+"Common.Alerts.MuteUserConfirm.Title" = "%@ をミュートしますか?";
"Common.Alerts.MuteUserSuccess.Title" = "%@ はミュートされました";
"Common.Alerts.NoTweetsFound.Title" = "ツイートが見つかりませんでした";
"Common.Alerts.PermissionDeniedFriendshipBlocked.Message" = "ユーザーのリクエストにより、このアカウントのフォローをブロックされています";
@@ -102,33 +119,33 @@
"Common.Alerts.RateLimitExceeded.Title" = "レート制限を超えました";
"Common.Alerts.ReportAndBlockUserSuccess.Title" = "%@ はスパムとして報告されブロックされました";
"Common.Alerts.ReportUserSuccess.Title" = "%@ はスパムとして報告されました";
-"Common.Alerts.SignOutUserConfirm.Message" = "Do you want to sign out?";
-"Common.Alerts.SignOutUserConfirm.Title" = "Sign out";
+"Common.Alerts.SignOutUserConfirm.Message" = "サインアウトしますか?";
+"Common.Alerts.SignOutUserConfirm.Title" = "サインアウト";
"Common.Alerts.TooManyRequests.Title" = "リクエストが多すぎます";
-"Common.Alerts.TootDeleted.Title" = "Toot Deleted";
-"Common.Alerts.TootFail.DraftSavedMessage" = "Your toot has been saved to Drafts.";
+"Common.Alerts.TootDeleted.Title" = "トゥートを削除しました";
+"Common.Alerts.TootFail.DraftSavedMessage" = "あなたのトゥートは下書きに保存されました。";
"Common.Alerts.TootFail.Message" = "もう一度やり直してください";
-"Common.Alerts.TootFail.Title" = "Failed to Toot";
-"Common.Alerts.TootPosted.Title" = "Toot Posted";
-"Common.Alerts.TootSending.Title" = "Sending toot";
+"Common.Alerts.TootFail.Title" = "トゥートに失敗しました";
+"Common.Alerts.TootPosted.Title" = "トゥートを投稿しました";
+"Common.Alerts.TootSending.Title" = "トゥートを送信中";
"Common.Alerts.TweetDeleted.Title" = "ツイートを削除しました";
-"Common.Alerts.TweetFail.DraftSavedMessage" = "Your tweet has been saved to Drafts.";
+"Common.Alerts.TweetFail.DraftSavedMessage" = "あなたのツイートは下書きに保存されました。";
"Common.Alerts.TweetFail.Message" = "もう一度やり直してください";
"Common.Alerts.TweetFail.Title" = "ツイートに失敗しました";
-"Common.Alerts.TweetPosted.Title" = "Tweet Posted";
+"Common.Alerts.TweetPosted.Title" = "ツイートを投稿しました";
"Common.Alerts.TweetSending.Title" = "ツイートを送信中";
"Common.Alerts.TweetSent.Title" = "ツイートを送信しました";
-"Common.Alerts.UnblockUserConfirm.Title" = "Do you want to unblock %@?";
+"Common.Alerts.UnblockUserConfirm.Title" = "%@ をブロック解除しますか?";
"Common.Alerts.UnblockUserSuccess.Title" = "%@ はブロック解除されました";
"Common.Alerts.UnfollowUser.Message" = "%@ のフォローを解除しますか?";
"Common.Alerts.UnfollowingSuccess.Title" = "フォロー解除に成功しました";
-"Common.Alerts.UnmuteUserConfirm.Title" = "Do you want to unmute %@?";
+"Common.Alerts.UnmuteUserConfirm.Title" = "%@ をミュート解除しますか?";
"Common.Alerts.UnmuteUserSuccess.Title" = "%@ はミュート解除されました";
"Common.Controls.Actions.Add" = "追加";
"Common.Controls.Actions.Browse" = "Browse";
"Common.Controls.Actions.Cancel" = "キャンセル";
"Common.Controls.Actions.Confirm" = "確認";
-"Common.Controls.Actions.Delete" = "Delete";
+"Common.Controls.Actions.Delete" = "削除";
"Common.Controls.Actions.Edit" = "編集";
"Common.Controls.Actions.Ok" = "OK";
"Common.Controls.Actions.OpenInSafari" = "Safari で開く";
@@ -138,7 +155,7 @@
"Common.Controls.Actions.SavePhoto" = "写真を保存";
"Common.Controls.Actions.ShareMedia" = "メディアを共有";
"Common.Controls.Actions.SignIn" = "サインイン";
-"Common.Controls.Actions.SignOut" = "Sign out";
+"Common.Controls.Actions.SignOut" = "サインアウト";
"Common.Controls.Actions.TakePhoto" = "写真を撮影";
"Common.Controls.Actions.Yes" = "はい";
"Common.Controls.Friendship.Actions.Block" = "ブロック";
@@ -149,7 +166,7 @@
"Common.Controls.Friendship.Actions.Pending" = "保留中";
"Common.Controls.Friendship.Actions.Report" = "報告";
"Common.Controls.Friendship.Actions.ReportAndBlock" = "報告とブロック";
-"Common.Controls.Friendship.Actions.Request" = "Request";
+"Common.Controls.Friendship.Actions.Request" = "リクエスト";
"Common.Controls.Friendship.Actions.Unblock" = "ブロック解除";
"Common.Controls.Friendship.Actions.Unfollow" = "フォロー解除";
"Common.Controls.Friendship.Actions.Unmute" = "ミュート解除";
@@ -166,59 +183,61 @@
"Common.Controls.ProfileDashboard.Following" = "フォロー中";
"Common.Controls.ProfileDashboard.Listed" = "リスト";
"Common.Controls.Status.Actions.Bookmark" = "ブックマーク";
+"Common.Controls.Status.Actions.Boost" = "Boost";
"Common.Controls.Status.Actions.CopyLink" = "リンクをコピー";
"Common.Controls.Status.Actions.CopyText" = "テキストをコピー";
"Common.Controls.Status.Actions.DeleteTweet" = "ツイートを削除";
-"Common.Controls.Status.Actions.PinOnProfile" = "Pin on Profile";
+"Common.Controls.Status.Actions.PinOnProfile" = "プロフィールに固定表示";
"Common.Controls.Status.Actions.Quote" = "引用";
-"Common.Controls.Status.Actions.Retweet" = "Retweet";
-"Common.Controls.Status.Actions.Share" = "Share";
+"Common.Controls.Status.Actions.Retweet" = "リツイート";
+"Common.Controls.Status.Actions.Share" = "共有";
+"Common.Controls.Status.Actions.ShareContent" = "コンテンツを共有";
"Common.Controls.Status.Actions.ShareLink" = "リンクを共有";
-"Common.Controls.Status.Actions.Translate" = "Translate";
-"Common.Controls.Status.Actions.UnpinFromProfile" = "Unpin from Profile";
-"Common.Controls.Status.Actions.Vote" = "Vote";
+"Common.Controls.Status.Actions.Translate" = "翻訳";
+"Common.Controls.Status.Actions.UnpinFromProfile" = "プロフィールへの固定を解除";
+"Common.Controls.Status.Actions.Vote" = "投票";
"Common.Controls.Status.Media" = "メディア";
"Common.Controls.Status.Poll.Expired" = "Closed";
-"Common.Controls.Status.Poll.TotalPeople" = "%@ people";
-"Common.Controls.Status.Poll.TotalPerson" = "%@ person";
-"Common.Controls.Status.Poll.TotalVote" = "%@ vote";
-"Common.Controls.Status.Poll.TotalVotes" = "%@ votes";
-"Common.Controls.Status.Thread.Show" = "Show this thread";
+"Common.Controls.Status.Poll.TotalPeople" = "%@ 人";
+"Common.Controls.Status.Poll.TotalPerson" = "%@ 人";
+"Common.Controls.Status.Poll.TotalVote" = "%@ 票";
+"Common.Controls.Status.Poll.TotalVotes" = "%@ 票";
+"Common.Controls.Status.Thread.Show" = "このスレッドを表示";
"Common.Controls.Status.UserBoosted" = "%@ boosted";
"Common.Controls.Status.UserRetweeted" = "%@ がリツイート";
"Common.Controls.Status.YouBoosted" = "You boosted";
-"Common.Controls.Status.YouRetweeted" = "You retweeted";
+"Common.Controls.Status.YouRetweeted" = "あなたがリツイート";
"Common.Controls.Timeline.LoadMore" = "さらに読み込む";
-"Common.Countable.Like.Multiple" = "%@ likes";
-"Common.Countable.Like.Single" = "%@ like";
-"Common.Countable.List.Multiple" = "%@ lists";
-"Common.Countable.List.Single" = "%@ list";
-"Common.Countable.Member.Multiple" = "%@ members";
-"Common.Countable.Member.Single" = "%@ member";
-"Common.Countable.Photo.Multiple" = "%@ photos";
-"Common.Countable.Photo.Single" = "%@ photo";
-"Common.Countable.Quote.Mutiple" = "%@ quotes";
-"Common.Countable.Quote.Single" = "%@ quote";
-"Common.Countable.Reply.Mutiple" = "%@ replies";
-"Common.Countable.Reply.Single" = "%@ reply";
-"Common.Countable.Retweet.Mutiple" = "%@ retweets";
-"Common.Countable.Retweet.Single" = "%@ retweet";
-"Common.Countable.Tweet.Multiple" = "%@ tweets";
-"Common.Countable.Tweet.Single" = "%@ tweet";
+"Common.Countable.Like.Multiple" = "%@ いいね";
+"Common.Countable.Like.Single" = "%@ いいね";
+"Common.Countable.List.Multiple" = "%@ リスト";
+"Common.Countable.List.Single" = "%@ リスト";
+"Common.Countable.Member.Multiple" = "%@ メンバー";
+"Common.Countable.Member.Single" = "%@ メンバー";
+"Common.Countable.Photo.Multiple" = "%@ 枚の写真";
+"Common.Countable.Photo.Single" = "%@ 枚の写真";
+"Common.Countable.Quote.Mutiple" = "%@ 引用";
+"Common.Countable.Quote.Single" = "%@ 引用";
+"Common.Countable.Reply.Mutiple" = "%@ 返信";
+"Common.Countable.Reply.Single" = "%@ 返信";
+"Common.Countable.Retweet.Mutiple" = "%@ リツイート";
+"Common.Countable.Retweet.Single" = "%@ リツイート";
+"Common.Countable.Tweet.Multiple" = "%@ ツイート";
+"Common.Countable.Tweet.Single" = "%@ ツイート";
"Common.Notification.Favourite" = "%@ さんがトゥートをお気に入りにしました";
-"Common.Notification.Follow" = "%@ followed you";
-"Common.Notification.FollowRequest" = "%@ has requested to follow you";
-"Common.Notification.Mentions" = "%@ mentions you";
-"Common.Notification.Messages.Content" = "%@ sent you a message";
-"Common.Notification.Messages.Title" = "New direct message";
+"Common.Notification.Follow" = "%@ がフォローしました";
+"Common.Notification.FollowRequest" = "%@ がフォローリクエストしました";
+"Common.Notification.Mentions" = "%@ がメンションしました";
+"Common.Notification.Messages.Content" = "%@ がメッセージを送信しました";
+"Common.Notification.Messages.Title" = "新しいダイレクト メッセージ";
"Common.Notification.OwnPoll" = "アンケートは終了しました";
"Common.Notification.Poll" = "投票したアンケートが締め切られました";
"Common.Notification.Reblog" = "%@ さんがトゥートをブーストしました";
-"Common.Notification.Status" = "%@ just posted";
+"Common.Notification.Status" = "%@ が投稿しました";
"Common.NotificationChannel.BackgroundProgresses.Name" = "Background progresses";
"Common.NotificationChannel.ContentInteractions.Description" = "Interactions like mentions and retweets";
"Common.NotificationChannel.ContentInteractions.Name" = "Interactions";
-"Common.NotificationChannel.ContentMessages.Description" = "Direct messages";
+"Common.NotificationChannel.ContentMessages.Description" = "ダイレクトメッセージ";
"Common.NotificationChannel.ContentMessages.Name" = "メッセージ";
"Scene.Authentication.Title" = "認証";
"Scene.Bookmark.Title" = "ブックマーク";
@@ -385,17 +404,17 @@
"Scene.Settings.Misc.Nitter.Input.Placeholder" = "Nitter Instance";
"Scene.Settings.Misc.Nitter.Input.Value" = "Instance URL";
"Scene.Settings.Misc.Nitter.Title" = "Third-party Twitter data provider";
-"Scene.Settings.Misc.Proxy.Enable.Description" = "Use proxy for all network requests";
-"Scene.Settings.Misc.Proxy.Enable.Title" = "Proxy";
-"Scene.Settings.Misc.Proxy.Password" = "Password";
-"Scene.Settings.Misc.Proxy.Port.Error" = "Proxy server port must be numbers";
-"Scene.Settings.Misc.Proxy.Port.Title" = "Port";
-"Scene.Settings.Misc.Proxy.Server" = "Server";
-"Scene.Settings.Misc.Proxy.Title" = "Proxy settings";
+"Scene.Settings.Misc.Proxy.Enable.Description" = "すべてのネットワーク要求にプロキシを使用する";
+"Scene.Settings.Misc.Proxy.Enable.Title" = "プロキシ";
+"Scene.Settings.Misc.Proxy.Password" = "パスワード";
+"Scene.Settings.Misc.Proxy.Port.Error" = "プロキシサーバーのポートは番号でなければなりません";
+"Scene.Settings.Misc.Proxy.Port.Title" = "ポート";
+"Scene.Settings.Misc.Proxy.Server" = "サーバー";
+"Scene.Settings.Misc.Proxy.Title" = "プロキシ設定";
"Scene.Settings.Misc.Proxy.Type.Http" = "HTTP";
-"Scene.Settings.Misc.Proxy.Type.Reverse" = "Reverse";
-"Scene.Settings.Misc.Proxy.Type.Title" = "Proxy type";
-"Scene.Settings.Misc.Proxy.Username" = "Username";
+"Scene.Settings.Misc.Proxy.Type.Reverse" = "リバース";
+"Scene.Settings.Misc.Proxy.Type.Title" = "プロキシの種類";
+"Scene.Settings.Misc.Proxy.Username" = "ユーザー名";
"Scene.Settings.Misc.Title" = "その他";
"Scene.Settings.Notification.Accounts" = "アカウント";
"Scene.Settings.Notification.NotificationSwitch" = "通知を表示";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.stringsdict
index 5c725633..7191bb92 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ja.lproj/Localizable.stringsdict
@@ -2,6 +2,132 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld media
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld characters
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ posts
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld posts
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld quotes
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld likes
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld replies
+
+
count.vote
NSStringLocalizedFormatKey
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.strings
index 233f6dee..d909f126 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "트위터 로고";
"Accessibility.Common.More" = "더 보기";
"Accessibility.Common.NetworkImage" = "네트워크 이미지";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "좋아요";
+"Accessibility.Common.Status.Actions.Menu" = "메뉴";
"Accessibility.Common.Status.Actions.Reply" = "답글";
"Accessibility.Common.Status.Actions.Retweet" = "리트윗";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Author avatar";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "위치";
"Accessibility.Common.Status.Media" = "미디어";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "리트윗함";
"Accessibility.Common.Video.Play" = "영상 보기";
"Accessibility.Scene.Compose.AddMention" = "답글 달기";
@@ -20,43 +30,50 @@
"Accessibility.Scene.Compose.Image" = "사진 넣기";
"Accessibility.Scene.Compose.Location.Disable" = "위치 끄기";
"Accessibility.Scene.Compose.Location.Enable" = "위치 켜기";
-"Accessibility.Scene.Compose.MediaInsert.Camera" = "Take Photo";
-"Accessibility.Scene.Compose.MediaInsert.Gif" = "Add GIF";
-"Accessibility.Scene.Compose.MediaInsert.Library" = "Browse Library";
-"Accessibility.Scene.Compose.MediaInsert.RecordVideo" = "Record Video";
+"Accessibility.Scene.Compose.MediaInsert.Camera" = "사진 찍기";
+"Accessibility.Scene.Compose.MediaInsert.Gif" = "움짤 넣기";
+"Accessibility.Scene.Compose.MediaInsert.Library" = "라이브러리 탐색";
+"Accessibility.Scene.Compose.MediaInsert.RecordVideo" = "영상 찍기";
"Accessibility.Scene.Compose.Send" = "보내기";
"Accessibility.Scene.Compose.Thread" = "타래 모드";
-"Accessibility.Scene.Gif.Search" = "Search GIF";
+"Accessibility.Scene.Gif.Search" = "움짤 찾기";
"Accessibility.Scene.Gif.Title" = "GIPHY";
"Accessibility.Scene.Home.Compose" = "쓰기";
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "계정 목록";
"Accessibility.Scene.Home.Menu" = "메뉴";
"Accessibility.Scene.ManageAccounts.Add" = "새로 만들기";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Current sign-in user: %@";
"Accessibility.Scene.Search.History" = "기록";
"Accessibility.Scene.Search.Save" = "저장";
"Accessibility.Scene.Settings.Display.FontSize" = "글꼴 크기";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Please enter Mastodon domain to sign-in";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter client authentication key setting";
"Accessibility.Scene.Timeline.LoadGap" = "불러오기";
"Accessibility.Scene.User.Location" = "위치";
"Accessibility.Scene.User.Tab.Favourite" = "좋아요";
"Accessibility.Scene.User.Tab.Media" = "미디어";
"Accessibility.Scene.User.Tab.Status" = "상태";
"Accessibility.Scene.User.Website" = "웹사이트";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Double tap and hold to display menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Double tap and hold to open the accounts panel";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Double tap to open profile";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "트위터는 %@ 규정을 어기면 계정을 정지시킵니다.";
"Common.Alerts.AccountSuspended.Title" = "계정 정지됨";
"Common.Alerts.AccountSuspended.TwitterRules" = "트위터 규정";
"Common.Alerts.AccountTemporarilyLocked.Message" = "트위터를 열어 잠금 풀기";
"Common.Alerts.AccountTemporarilyLocked.Title" = "계정이 일시적으로 잠겼습니다.";
-"Common.Alerts.BlockUserConfirm.Title" = "Do you want to block %@?";
+"Common.Alerts.BlockUserConfirm.Title" = "%@를 차단할까요?";
"Common.Alerts.BlockUserSuccess.Title" = "%@를 차단했습니다.";
"Common.Alerts.CancelFollowRequest.Message" = "%@의 팔로우 요청을 취소할까요?";
-"Common.Alerts.DeleteTootConfirm.Message" = "Do you want to delete this toot?";
-"Common.Alerts.DeleteTootConfirm.Title" = "Delete Toot";
-"Common.Alerts.DeleteTweetConfirm.Message" = "Do you want to delete this tweet?";
-"Common.Alerts.DeleteTweetConfirm.Title" = "Delete Tweet";
+"Common.Alerts.DeleteTootConfirm.Message" = "이 툿을 지울까요?";
+"Common.Alerts.DeleteTootConfirm.Title" = "툿을 지웁니다.";
+"Common.Alerts.DeleteTweetConfirm.Message" = "이 트윗을 지울까요?";
+"Common.Alerts.DeleteTweetConfirm.Title" = "트윗을 지웁니다.";
"Common.Alerts.FailedToBlockUser.Message" = "다시 해보세요.";
"Common.Alerts.FailedToBlockUser.Title" = "%@를 차단하지 못했습니다.";
"Common.Alerts.FailedToDeleteToot.Message" = "다시 해보세요.";
-"Common.Alerts.FailedToDeleteToot.Title" = "Failed to Delete Toot";
+"Common.Alerts.FailedToDeleteToot.Title" = "툿을 지우지 못했습니다.";
"Common.Alerts.FailedToDeleteTweet.Message" = "다시 해보세요.";
"Common.Alerts.FailedToDeleteTweet.Title" = "트윗을 지우지 못했습니다.";
"Common.Alerts.FailedToFollowing.Message" = "다시 해보세요.";
@@ -88,7 +105,7 @@
"Common.Alerts.MediaSaved.Title" = "저장했습니다";
"Common.Alerts.MediaSaving.Title" = "저장하기";
"Common.Alerts.MediaSharing.Title" = "다운로드 뒤 미디어가 공유됩니다";
-"Common.Alerts.MuteUserConfirm.Title" = "Do you want to mute %@?";
+"Common.Alerts.MuteUserConfirm.Title" = "%@를 뮤트할까요?";
"Common.Alerts.MuteUserSuccess.Title" = "%@를 뮤트했습니다.";
"Common.Alerts.NoTweetsFound.Title" = "트윗을 찾을 수 없습니다.";
"Common.Alerts.PermissionDeniedFriendshipBlocked.Message" = "이 사용자의 요청으로 이 계정을 팔로우하는 것이 차단되어 있습니다.";
@@ -102,33 +119,33 @@
"Common.Alerts.RateLimitExceeded.Title" = "한도를 넘어서 제한이 걸렸습니다.";
"Common.Alerts.ReportAndBlockUserSuccess.Title" = "%@는 스팸으로 신고받아 차단되었습니다.";
"Common.Alerts.ReportUserSuccess.Title" = "%@는 스팸으로 신고됐습니다.";
-"Common.Alerts.SignOutUserConfirm.Message" = "Do you want to sign out?";
-"Common.Alerts.SignOutUserConfirm.Title" = "Sign out";
+"Common.Alerts.SignOutUserConfirm.Message" = "로그아웃 할까요?";
+"Common.Alerts.SignOutUserConfirm.Title" = "로그아웃 됐습니다.";
"Common.Alerts.TooManyRequests.Title" = "요청이 몰렸습니다.";
-"Common.Alerts.TootDeleted.Title" = "Toot Deleted";
-"Common.Alerts.TootFail.DraftSavedMessage" = "Your toot has been saved to Drafts.";
+"Common.Alerts.TootDeleted.Title" = "툿을 지웠습니다.";
+"Common.Alerts.TootFail.DraftSavedMessage" = "툿이 보관함으로 이동되었습니다.";
"Common.Alerts.TootFail.Message" = "다시 해보세요.";
-"Common.Alerts.TootFail.Title" = "Failed to Toot";
-"Common.Alerts.TootPosted.Title" = "Toot Posted";
-"Common.Alerts.TootSending.Title" = "Sending toot";
+"Common.Alerts.TootFail.Title" = "툿을 실패했습니다.";
+"Common.Alerts.TootPosted.Title" = "툿했습니다.";
+"Common.Alerts.TootSending.Title" = "툿을 보냅니다.";
"Common.Alerts.TweetDeleted.Title" = "트윗을 지웠습니다.";
-"Common.Alerts.TweetFail.DraftSavedMessage" = "Your tweet has been saved to Drafts.";
+"Common.Alerts.TweetFail.DraftSavedMessage" = "트윗이 보관함으로 이동되었습니다.";
"Common.Alerts.TweetFail.Message" = "다시 해보세요.";
"Common.Alerts.TweetFail.Title" = "트윗이 올라가지 않았습니다.";
-"Common.Alerts.TweetPosted.Title" = "Tweet Posted";
+"Common.Alerts.TweetPosted.Title" = "트윗했습니다.";
"Common.Alerts.TweetSending.Title" = "트윗을 보냅니다";
"Common.Alerts.TweetSent.Title" = "트윗 보냄";
-"Common.Alerts.UnblockUserConfirm.Title" = "Do you want to unblock %@?";
+"Common.Alerts.UnblockUserConfirm.Title" = "%@의 차단을 풀까요?";
"Common.Alerts.UnblockUserSuccess.Title" = "%@의 차단을 풉니다.";
"Common.Alerts.UnfollowUser.Message" = "%@를 그만 팔로우할까요?";
"Common.Alerts.UnfollowingSuccess.Title" = "팔로우를 끊었습니다.";
-"Common.Alerts.UnmuteUserConfirm.Title" = "Do you want to unmute %@?";
+"Common.Alerts.UnmuteUserConfirm.Title" = "%@를 이제 뮤트하지 말까요?";
"Common.Alerts.UnmuteUserSuccess.Title" = "%@를 뮤트하지 않습니다.";
"Common.Controls.Actions.Add" = "새로 만들기";
-"Common.Controls.Actions.Browse" = "Browse";
+"Common.Controls.Actions.Browse" = "탐색";
"Common.Controls.Actions.Cancel" = "돌아가기";
"Common.Controls.Actions.Confirm" = "확인";
-"Common.Controls.Actions.Delete" = "Delete";
+"Common.Controls.Actions.Delete" = "지우기";
"Common.Controls.Actions.Edit" = "고치기";
"Common.Controls.Actions.Ok" = "확인";
"Common.Controls.Actions.OpenInSafari" = "사파리에서 보기";
@@ -138,18 +155,18 @@
"Common.Controls.Actions.SavePhoto" = "사진 저장하기";
"Common.Controls.Actions.ShareMedia" = "미디어 공유하기";
"Common.Controls.Actions.SignIn" = "로그인";
-"Common.Controls.Actions.SignOut" = "Sign out";
+"Common.Controls.Actions.SignOut" = "로그아웃 됐습니다.";
"Common.Controls.Actions.TakePhoto" = "사진 찍기";
"Common.Controls.Actions.Yes" = "네";
"Common.Controls.Friendship.Actions.Block" = "차단하기";
-"Common.Controls.Friendship.Actions.Blocked" = "Blocked";
+"Common.Controls.Friendship.Actions.Blocked" = "차단됨";
"Common.Controls.Friendship.Actions.Follow" = "팔로우하기";
"Common.Controls.Friendship.Actions.Following" = "팔로우 중";
"Common.Controls.Friendship.Actions.Mute" = "숨기기";
"Common.Controls.Friendship.Actions.Pending" = "대기중";
"Common.Controls.Friendship.Actions.Report" = "신고하기";
"Common.Controls.Friendship.Actions.ReportAndBlock" = "신고하고 차단하기";
-"Common.Controls.Friendship.Actions.Request" = "Request";
+"Common.Controls.Friendship.Actions.Request" = "요청";
"Common.Controls.Friendship.Actions.Unblock" = "차단 풀기";
"Common.Controls.Friendship.Actions.Unfollow" = "팔로우 끊기";
"Common.Controls.Friendship.Actions.Unmute" = "숨기기 풀기";
@@ -161,21 +178,23 @@
"Common.Controls.Friendship.UserIsFollowingYou" = "%@가 날 팔로우하고 있음";
"Common.Controls.Friendship.UserIsNotFollowingYou" = "%@가 날 팔로우하지 않음";
"Common.Controls.Ios.PhotoLibrary" = "갤러리";
-"Common.Controls.List.NoResults" = "No results";
+"Common.Controls.List.NoResults" = "결과 없음";
"Common.Controls.ProfileDashboard.Followers" = "팔로워";
"Common.Controls.ProfileDashboard.Following" = "팔로우 중";
"Common.Controls.ProfileDashboard.Listed" = "담긴 리스트";
"Common.Controls.Status.Actions.Bookmark" = "즐겨찾기";
+"Common.Controls.Status.Actions.Boost" = "Boost";
"Common.Controls.Status.Actions.CopyLink" = "링크 복사하기";
"Common.Controls.Status.Actions.CopyText" = "글 복사하기";
"Common.Controls.Status.Actions.DeleteTweet" = "트윗 지우기";
-"Common.Controls.Status.Actions.PinOnProfile" = "Pin on Profile";
+"Common.Controls.Status.Actions.PinOnProfile" = "프로필에 고정";
"Common.Controls.Status.Actions.Quote" = "인용";
"Common.Controls.Status.Actions.Retweet" = "리트윗";
-"Common.Controls.Status.Actions.Share" = "Share";
+"Common.Controls.Status.Actions.Share" = "공유";
+"Common.Controls.Status.Actions.ShareContent" = "Share content";
"Common.Controls.Status.Actions.ShareLink" = "링크 공유하기";
-"Common.Controls.Status.Actions.Translate" = "Translate";
-"Common.Controls.Status.Actions.UnpinFromProfile" = "Unpin from Profile";
+"Common.Controls.Status.Actions.Translate" = "번역";
+"Common.Controls.Status.Actions.UnpinFromProfile" = "프로필에서 고정 풀기";
"Common.Controls.Status.Actions.Vote" = "투표";
"Common.Controls.Status.Media" = "미디어";
"Common.Controls.Status.Poll.Expired" = "닫힘";
@@ -184,10 +203,10 @@
"Common.Controls.Status.Poll.TotalVote" = "%@표";
"Common.Controls.Status.Poll.TotalVotes" = "%@표";
"Common.Controls.Status.Thread.Show" = "이 타래 보기";
-"Common.Controls.Status.UserBoosted" = "%@ boosted";
+"Common.Controls.Status.UserBoosted" = "%@이 부스트했습니다.";
"Common.Controls.Status.UserRetweeted" = "%@가 리트윗함";
-"Common.Controls.Status.YouBoosted" = "You boosted";
-"Common.Controls.Status.YouRetweeted" = "You retweeted";
+"Common.Controls.Status.YouBoosted" = "내가 부스트했습니다.";
+"Common.Controls.Status.YouRetweeted" = "내가 리트윗했습니다.";
"Common.Controls.Timeline.LoadMore" = "더 불러오기";
"Common.Countable.Like.Multiple" = "%@개 좋아요";
"Common.Countable.Like.Single" = "%@개 좋아요";
@@ -238,10 +257,10 @@
"Scene.Compose.Visibility.Private" = "비공개";
"Scene.Compose.Visibility.Public" = "공개";
"Scene.Compose.Visibility.Unlisted" = "리스트 되지 않음";
-"Scene.Compose.VisibilityDescription.Direct" = "Visible for mentioned users only";
-"Scene.Compose.VisibilityDescription.Private" = "Visible for followers only";
-"Scene.Compose.VisibilityDescription.Public" = "Visible for all, shown in public timelines";
-"Scene.Compose.VisibilityDescription.Unlisted" = "Visible for all, but not in public timelines";
+"Scene.Compose.VisibilityDescription.Direct" = "멘션한 사람에게만 보이기";
+"Scene.Compose.VisibilityDescription.Private" = "팔로워들에게만 보이기";
+"Scene.Compose.VisibilityDescription.Public" = "모두에게 보이기, 퍼블릭 타임라인에 나타납니다.";
+"Scene.Compose.VisibilityDescription.Unlisted" = "모두에게 보이지만, 퍼블릭 타임라인에 나타나지 않습니다.";
"Scene.Compose.Vote.Expiration.1Day" = "하루";
"Scene.Compose.Vote.Expiration.1Hour" = "1시간";
"Scene.Compose.Vote.Expiration.30Min" = "30분";
@@ -250,7 +269,7 @@
"Scene.Compose.Vote.Expiration.6Hour" = "6시간";
"Scene.Compose.Vote.Expiration.7Day" = "일주일";
"Scene.Compose.Vote.Multiple" = "여러 개 고르기";
-"Scene.Compose.Vote.PlaceholderIndex" = "Choice %d";
+"Scene.Compose.Vote.PlaceholderIndex" = "%d 고르기";
"Scene.ComposeHashtagSearch.SearchPlaceholder" = "해시태그 찾기";
"Scene.ComposeUserSearch.SearchPlaceholder" = "사용자 찾기";
"Scene.Drafts.Actions.DeleteDraft" = "지우기";
@@ -258,7 +277,7 @@
"Scene.Drafts.Title" = "임시 보관함";
"Scene.Drawer.ManageAccounts" = "계정 관리";
"Scene.Drawer.SignIn" = "로그인";
-"Scene.Federated.Title" = "Federated";
+"Scene.Federated.Title" = "연합";
"Scene.Followers.Title" = "팔로워";
"Scene.Following.Title" = "팔로우 중";
"Scene.Likes.Title" = "좋아요";
@@ -296,13 +315,13 @@
"Scene.ListsUsers.Add.Title" = "멤버 추가";
"Scene.ListsUsers.MenuActions.Add" = "새로 만들기";
"Scene.ListsUsers.MenuActions.Remove" = "지우기";
-"Scene.Local.Title" = "Local";
+"Scene.Local.Title" = "로컬";
"Scene.ManageAccounts.DeleteAccount" = "계정 지우기";
"Scene.ManageAccounts.Title" = "계정";
"Scene.Mentions.Title" = "답글";
"Scene.Messages.Action.CopyText" = "메시지 글자 복사";
"Scene.Messages.Action.Delete" = "메시지 지우기";
-"Scene.Messages.Error.NotSupported" = "The Current account does not support direct messages";
+"Scene.Messages.Error.NotSupported" = "이 계정은 DM을 보낼 수 없습니다.";
"Scene.Messages.Expanded.Photo" = "[사진]";
"Scene.Messages.Icon.Failed" = "메시지를 보내지 못했습니다";
"Scene.Messages.NewConversation.Search" = "사람 찾기";
@@ -311,7 +330,7 @@
"Scene.Notification.Tabs.All" = "모두";
"Scene.Notification.Tabs.Mentions" = "답글";
"Scene.Notification.Title" = "알림";
-"Scene.Profile.Fields.JoinedInDate" = "Joined in %@";
+"Scene.Profile.Fields.JoinedInDate" = "%@에 가입함";
"Scene.Profile.Filter.All" = "모든 트윗";
"Scene.Profile.Filter.ExcludeReplies" = "답글 빼기";
"Scene.Profile.HideReply" = "답글 숨기기";
@@ -324,8 +343,8 @@
"Scene.Search.ShowMore" = "더 보기";
"Scene.Search.Tabs.Hashtag" = "해시태그";
"Scene.Search.Tabs.Media" = "미디어";
-"Scene.Search.Tabs.People" = "People";
-"Scene.Search.Tabs.Toots" = "Toots";
+"Scene.Search.Tabs.People" = "사람들";
+"Scene.Search.Tabs.Toots" = "툿";
"Scene.Search.Tabs.Tweets" = "트윗";
"Scene.Search.Tabs.Users" = "사용자";
"Scene.Search.Title" = "찾기";
@@ -357,7 +376,7 @@
"Scene.Settings.Display.Media.AutoPlayback" = "자동 재생";
"Scene.Settings.Display.Media.Automatic" = "자동";
"Scene.Settings.Display.Media.MediaPreviews" = "미디어 미리보기";
-"Scene.Settings.Display.Media.MuteByDefault" = "Mute by default";
+"Scene.Settings.Display.Media.MuteByDefault" = "기본으로 숨기기";
"Scene.Settings.Display.Media.Off" = "끄기";
"Scene.Settings.Display.Preview.ThankForUsingTwidereX" = "@TwidereProject를 써주셔서 고맙습니다!";
"Scene.Settings.Display.SectionHeader.DateFormat" = "날짜 형식";
@@ -381,7 +400,7 @@
"Scene.Settings.Misc.Nitter.Dialog.Usage.ProjectButton" = "프로젝트 소개 보기";
"Scene.Settings.Misc.Nitter.Dialog.Usage.Title" = "다음의 제3 트위터 정보 제공자를 사용 중";
"Scene.Settings.Misc.Nitter.Input.Description" = "개인정보보호에 초점을 둔 트위터 프론트엔드 대체 기능";
-"Scene.Settings.Misc.Nitter.Input.Invalid" = "Nitter instance URL is invalid, e.g. https://nitter.net";
+"Scene.Settings.Misc.Nitter.Input.Invalid" = "Nitter의 Instance URL이 유효하지 않습니다. 예) https://nitter.net";
"Scene.Settings.Misc.Nitter.Input.Placeholder" = "Nitter 인스턴스";
"Scene.Settings.Misc.Nitter.Input.Value" = "Instance URL";
"Scene.Settings.Misc.Nitter.Title" = "제3의 트위터 정보 제공자";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.stringsdict
index 5c725633..23182c92 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/ko.lproj/Localizable.stringsdict
@@ -2,6 +2,132 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld media
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ Input limit remains %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld characters
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ posts
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld posts
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld quotes
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld likes
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld replies
+
+
count.vote
NSStringLocalizedFormatKey
@@ -13,7 +139,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld votes
+ %ld 표
count.people
@@ -27,7 +153,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld people
+ %ld 명
count.people.talking
@@ -41,7 +167,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld people talking
+ %ld 명이 이야기 중
date.year.left
@@ -55,7 +181,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld years left
+ %ld년 남음
date.month.left
@@ -69,7 +195,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld months left
+ %ld달 남음
date.day.left
@@ -83,7 +209,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld days left
+ %ld일 남음
date.hour.left
@@ -97,7 +223,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld hours left
+ %ld시간 남음
date.minute.left
@@ -111,7 +237,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld minutes left
+ %ld분 남음
date.second.left
@@ -125,7 +251,7 @@
NSStringFormatValueTypeKey
ld
other
- %ld seconds left
+ %ld초 남음
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.strings
index a2b566a3..c7491656 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "Logo do Twitter";
"Accessibility.Common.More" = "Mais";
"Accessibility.Common.NetworkImage" = "Imagem da Rede";
+"Accessibility.Common.Status.Actions.HideContent" = "Hide content";
+"Accessibility.Common.Status.Actions.HideMedia" = "Hide media";
"Accessibility.Common.Status.Actions.Like" = "Curtir";
+"Accessibility.Common.Status.Actions.Menu" = "Menu";
"Accessibility.Common.Status.Actions.Reply" = "Responder";
"Accessibility.Common.Status.Actions.Retweet" = "Retweet";
+"Accessibility.Common.Status.Actions.RevealContent" = "Reveal content";
+"Accessibility.Common.Status.Actions.RevealMedia" = "Reveal media";
+"Accessibility.Common.Status.AuthorAvatar" = "Avatar do autor";
+"Accessibility.Common.Status.Boosted" = "Boosted";
+"Accessibility.Common.Status.ContentWarning" = "Content Warning";
+"Accessibility.Common.Status.Liked" = "Liked";
"Accessibility.Common.Status.Location" = "Localização";
"Accessibility.Common.Status.Media" = "Mídia";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "Poll option";
"Accessibility.Common.Status.Retweeted" = "Retweetado";
"Accessibility.Common.Video.Play" = "Reproduzir vídeo";
"Accessibility.Scene.Compose.AddMention" = "Adicionar menção";
@@ -32,15 +42,22 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "Abaixar Conta";
"Accessibility.Scene.Home.Menu" = "Menu";
"Accessibility.Scene.ManageAccounts.Add" = "Adicionar";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "Usuário de login atual: %@";
"Accessibility.Scene.Search.History" = "Histórico";
"Accessibility.Scene.Search.Save" = "Salvar";
"Accessibility.Scene.Settings.Display.FontSize" = "Tamanho da Fonte";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "Insira o domínio Mastodon para fazer login";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Configuração da chave de autenticação do cliente do Twitter";
"Accessibility.Scene.Timeline.LoadGap" = "Carregar";
"Accessibility.Scene.User.Location" = "Localização";
"Accessibility.Scene.User.Tab.Favourite" = "Favorito";
"Accessibility.Scene.User.Tab.Media" = "Mídia";
"Accessibility.Scene.User.Tab.Status" = "Status";
"Accessibility.Scene.User.Website" = "Site";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "Toque duas vezes e segure para exibir o menu";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "Toque duas vezes e segure para abrir o painel de contas";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "Toque duas vezes para abrir o perfil";
+"Accessibility.VoiceOver.Selected" = "Selected";
"Common.Alerts.AccountSuspended.Message" = "O Twitter suspende contas que violam as %@";
"Common.Alerts.AccountSuspended.Title" = "Conta Suspensa";
"Common.Alerts.AccountSuspended.TwitterRules" = "Regras do Twitter";
@@ -90,7 +107,7 @@
"Common.Alerts.MediaSharing.Title" = "A mídia será compartilhada depois que o download for concluído";
"Common.Alerts.MuteUserConfirm.Title" = "Você quer silenciar %@?";
"Common.Alerts.MuteUserSuccess.Title" = "%@ foi silenciado";
-"Common.Alerts.NoTweetsFound.Title" = "Nenhum Tweet Encontrado";
+"Common.Alerts.NoTweetsFound.Title" = "Sem Tweets Encontrados";
"Common.Alerts.PermissionDeniedFriendshipBlocked.Message" = "Você foi impedido de seguir esta conta a pedido do usuário";
"Common.Alerts.PermissionDeniedFriendshipBlocked.Title" = "Permissão Negada";
"Common.Alerts.PermissionDeniedNotAuthorized.Message" = "Desculpe, você não está autorizado";
@@ -104,7 +121,7 @@
"Common.Alerts.ReportUserSuccess.Title" = "%@ foi reportado por spam";
"Common.Alerts.SignOutUserConfirm.Message" = "Você quer encerrar esta sessão?";
"Common.Alerts.SignOutUserConfirm.Title" = "Encerrar sessão";
-"Common.Alerts.TooManyRequests.Title" = "Muitas solicitações";
+"Common.Alerts.TooManyRequests.Title" = "Muitos Pedidos";
"Common.Alerts.TootDeleted.Title" = "Toot Excluído";
"Common.Alerts.TootFail.DraftSavedMessage" = "Seu toot foi salvo nos Rascunhos.";
"Common.Alerts.TootFail.Message" = "Por favor, tente novamente";
@@ -161,11 +178,12 @@
"Common.Controls.Friendship.UserIsFollowingYou" = "%@ está seguindo você";
"Common.Controls.Friendship.UserIsNotFollowingYou" = "%@ não está seguindo você";
"Common.Controls.Ios.PhotoLibrary" = "Biblioteca de Fotos";
-"Common.Controls.List.NoResults" = "No results";
+"Common.Controls.List.NoResults" = "Nenhum resultado";
"Common.Controls.ProfileDashboard.Followers" = "Seguidores";
"Common.Controls.ProfileDashboard.Following" = "Seguindo";
"Common.Controls.ProfileDashboard.Listed" = "Listado";
"Common.Controls.Status.Actions.Bookmark" = "Favorito";
+"Common.Controls.Status.Actions.Boost" = "Impulso";
"Common.Controls.Status.Actions.CopyLink" = "Copiar link";
"Common.Controls.Status.Actions.CopyText" = "Copiar texto";
"Common.Controls.Status.Actions.DeleteTweet" = "Excluir tweet";
@@ -173,6 +191,7 @@
"Common.Controls.Status.Actions.Quote" = "Citação";
"Common.Controls.Status.Actions.Retweet" = "Retweet";
"Common.Controls.Status.Actions.Share" = "Compartilhar";
+"Common.Controls.Status.Actions.ShareContent" = "Compartilhar conteúdo";
"Common.Controls.Status.Actions.ShareLink" = "Compartilhar link";
"Common.Controls.Status.Actions.Translate" = "Traduzir";
"Common.Controls.Status.Actions.UnpinFromProfile" = "Desafixar do Perfil";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.stringsdict
index cc5e803c..f1b55c10 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/pt-BR.lproj/Localizable.stringsdict
@@ -2,6 +2,150 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 mídia
+ other
+ %ld mídias
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ O limite de entrada permanece %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 caractere
+ other
+ %ld caracteres
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ postagem
+ other
+ postagens
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 postagem
+ other
+ %ld postagens
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 citação
+ other
+ %ld citações
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 curtida
+ other
+ %ld curtidas
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 retweet
+ other
+ %ld retweets
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 reblog
+ other
+ %ld reblogs
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ one
+ 1 resposta
+ other
+ %ld respostas
+
+
count.vote
NSStringLocalizedFormatKey
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.strings b/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.strings
index 79f1be48..234b4a5a 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.strings
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.strings
@@ -8,11 +8,21 @@
"Accessibility.Common.Logo.Twitter" = "Twitter 徽标";
"Accessibility.Common.More" = "更多";
"Accessibility.Common.NetworkImage" = "网络图像";
+"Accessibility.Common.Status.Actions.HideContent" = "隐藏内容";
+"Accessibility.Common.Status.Actions.HideMedia" = "隐藏媒体";
"Accessibility.Common.Status.Actions.Like" = "喜欢";
+"Accessibility.Common.Status.Actions.Menu" = "菜单";
"Accessibility.Common.Status.Actions.Reply" = "回复";
"Accessibility.Common.Status.Actions.Retweet" = "转推";
+"Accessibility.Common.Status.Actions.RevealContent" = "显示内容";
+"Accessibility.Common.Status.Actions.RevealMedia" = "显示媒体";
+"Accessibility.Common.Status.AuthorAvatar" = "作者头像";
+"Accessibility.Common.Status.Boosted" = "已转嘟";
+"Accessibility.Common.Status.ContentWarning" = "内容警告";
+"Accessibility.Common.Status.Liked" = "已喜欢";
"Accessibility.Common.Status.Location" = "地理位置";
"Accessibility.Common.Status.Media" = "媒体";
+"Accessibility.Common.Status.PollOptionOrdinalPrefix" = "投票选项";
"Accessibility.Common.Status.Retweeted" = "转推了";
"Accessibility.Common.Video.Play" = "播放视频";
"Accessibility.Scene.Compose.AddMention" = "添加提及";
@@ -32,20 +42,27 @@
"Accessibility.Scene.Home.Drawer.AccountDropdown" = "账户下拉列表";
"Accessibility.Scene.Home.Menu" = "菜单";
"Accessibility.Scene.ManageAccounts.Add" = "添加";
+"Accessibility.Scene.ManageAccounts.CurrentSignInUser" = "当前登录用户:%@";
"Accessibility.Scene.Search.History" = "历史";
"Accessibility.Scene.Search.Save" = "保存";
"Accessibility.Scene.Settings.Display.FontSize" = "字体大小";
+"Accessibility.Scene.SignIn.PleaseEnterMastodonDomainToSignIn" = "请输入要登录的 Mastodon 域名";
+"Accessibility.Scene.SignIn.TwitterClientAuthenticationKeySetting" = "Twitter 客户端认证密钥自定义设置";
"Accessibility.Scene.Timeline.LoadGap" = "加载";
"Accessibility.Scene.User.Location" = "地理位置";
"Accessibility.Scene.User.Tab.Favourite" = "收藏";
"Accessibility.Scene.User.Tab.Media" = "媒体";
"Accessibility.Scene.User.Tab.Status" = "推文";
"Accessibility.Scene.User.Website" = "站点";
+"Accessibility.VoiceOver.DoubleTapAndHoldToDisplayMenu" = "轻点两下并按住以显示菜单";
+"Accessibility.VoiceOver.DoubleTapAndHoldToOpenTheAccountsPanel" = "轻点两下并按住打开账户列表";
+"Accessibility.VoiceOver.DoubleTapToOpenProfile" = "轻点两下打开个人资料";
+"Accessibility.VoiceOver.Selected" = "已选中";
"Common.Alerts.AccountSuspended.Message" = "Twitter 会冻结违反 %@ 的账号";
"Common.Alerts.AccountSuspended.Title" = "账号已被冻结";
"Common.Alerts.AccountSuspended.TwitterRules" = "Twitter 规则";
"Common.Alerts.AccountTemporarilyLocked.Message" = "打开 Twitter 以解锁";
-"Common.Alerts.AccountTemporarilyLocked.Title" = "帐户临时锁定";
+"Common.Alerts.AccountTemporarilyLocked.Title" = "账户临时锁定";
"Common.Alerts.BlockUserConfirm.Title" = "是否要屏蔽 %@?";
"Common.Alerts.BlockUserSuccess.Title" = "%@ 已被屏蔽";
"Common.Alerts.CancelFollowRequest.Message" = "取消关注 %@ 的请求?";
@@ -166,6 +183,7 @@
"Common.Controls.ProfileDashboard.Following" = "已关注";
"Common.Controls.ProfileDashboard.Listed" = "列表中";
"Common.Controls.Status.Actions.Bookmark" = "书签";
+"Common.Controls.Status.Actions.Boost" = "转嘟";
"Common.Controls.Status.Actions.CopyLink" = "复制链接";
"Common.Controls.Status.Actions.CopyText" = "复制文本";
"Common.Controls.Status.Actions.DeleteTweet" = "删除推文";
@@ -173,6 +191,7 @@
"Common.Controls.Status.Actions.Quote" = "引用";
"Common.Controls.Status.Actions.Retweet" = "转推";
"Common.Controls.Status.Actions.Share" = "分享";
+"Common.Controls.Status.Actions.ShareContent" = "分享内容";
"Common.Controls.Status.Actions.ShareLink" = "分享链接";
"Common.Controls.Status.Actions.Translate" = "翻译";
"Common.Controls.Status.Actions.UnpinFromProfile" = "在个人资料页面取消置顶";
diff --git a/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict b/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict
index 0637774a..3bf18685 100644
--- a/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict
+++ b/TwidereSDK/Sources/TwidereLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict
@@ -2,6 +2,132 @@
+ count.media
+
+ NSStringLocalizedFormatKey
+ %#@count_media@
+ count_media
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 个媒体
+
+
+ count.input_limit_remains
+
+ NSStringLocalizedFormatKey
+ 输入字符限制剩余 %#@character_count@
+ character_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 个字符
+
+
+ count.metric_formatted.post
+
+ NSStringLocalizedFormatKey
+ %@ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ 帖子
+
+
+ count.post
+
+ NSStringLocalizedFormatKey
+ %#@post_count@
+ post_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 个帖子
+
+
+ count.quote
+
+ NSStringLocalizedFormatKey
+ %#@quote_count@
+ quote_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 个引用
+
+
+ count.like
+
+ NSStringLocalizedFormatKey
+ %#@like_count@
+ like_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 个赞
+
+
+ count.retweet
+
+ NSStringLocalizedFormatKey
+ %#@retweet_count@
+ retweet_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 条转推
+
+
+ count.reblog
+
+ NSStringLocalizedFormatKey
+ %#@reblog_count@
+ reblog_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 条转嘟
+
+
+ count.reply
+
+ NSStringLocalizedFormatKey
+ %#@reply_count@
+ reply_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ %ld 个回复
+
+
count.vote
NSStringLocalizedFormatKey
diff --git a/TwidereSDK/Sources/TwidereUI/Container/MediaGridContainerView.swift b/TwidereSDK/Sources/TwidereUI/Container/MediaGridContainerView.swift
index 65829b06..16cf1e1a 100644
--- a/TwidereSDK/Sources/TwidereUI/Container/MediaGridContainerView.swift
+++ b/TwidereSDK/Sources/TwidereUI/Container/MediaGridContainerView.swift
@@ -59,6 +59,9 @@ public final class MediaGridContainerView: UIView {
let sensitiveToggleButton: HitTestExpandedButton = {
let button = HitTestExpandedButton(type: .system)
button.setImage(Asset.Human.eyeSlashMini.image.withRenderingMode(.alwaysTemplate), for: .normal)
+ button.isAccessibilityElement = true
+ button.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.hideMedia
+ button.accessibilityTraits = .button
return button
}()
@@ -67,6 +70,9 @@ public final class MediaGridContainerView: UIView {
overlay.layer.masksToBounds = true
overlay.layer.cornerRadius = MediaView.cornerRadius
overlay.layer.cornerCurve = .continuous
+ overlay.isAccessibilityElement = true
+ overlay.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.revealMedia
+ overlay.accessibilityTraits = .button
return overlay
}()
@@ -335,3 +341,17 @@ extension MediaGridContainerView: ContentWarningOverlayViewDelegate {
delegate?.mediaGridContainerView(self, toggleContentWarningOverlayViewDisplay: contentWarningOverlayView)
}
}
+
+extension MediaGridContainerView {
+ public override var accessibilityElements: [Any]? {
+ get {
+ if viewModel.isContentWarningOverlayDisplay == true {
+ return [contentWarningOverlayView]
+ } else {
+ return [sensitiveToggleButton] + mediaViews
+ }
+
+ }
+ set { }
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereUI/Container/ProfileAvatarView.swift b/TwidereSDK/Sources/TwidereUI/Container/ProfileAvatarView.swift
index d6922e93..ee73499e 100644
--- a/TwidereSDK/Sources/TwidereUI/Container/ProfileAvatarView.swift
+++ b/TwidereSDK/Sources/TwidereUI/Container/ProfileAvatarView.swift
@@ -11,16 +11,12 @@ import Combine
public final class ProfileAvatarView: UIView {
- static let primitiveAvatarButtonSize = CGSize(width: 88, height: 88)
- static let primitiveBadgeImageViewSize = CGSize(width: 24, height: 24)
-
var disposeBag = Set()
public let avatarContainerView = UIView()
public let avatarButton: AvatarButton = {
- let frame = CGRect(origin: .zero, size: ProfileAvatarView.primitiveAvatarButtonSize)
- let button = AvatarButton(frame: frame)
+ let button = AvatarButton(frame: .zero)
button.avatarImageView.image = .placeholder(color: .systemFill)
return button
}()
@@ -32,23 +28,19 @@ public final class ProfileAvatarView: UIView {
var badgeImageViewHeightLayoutConstraint: NSLayoutConstraint!
private let badgeMaskImageView = UIImageView()
+
+ private(set) var dimension: Dimension?
- let layoutDidChange = PassthroughSubject()
-
- public var dimension: CGFloat = 44.0 {
- didSet {
- updateScale()
- }
- }
@Published public var avatarStyle: CornerStyle = .circle
@Published public var badge: Badge = .none
+ let layoutDidChange = PassthroughSubject()
- override init(frame: CGRect) {
+ public override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
- required init?(coder: NSCoder) {
+ public required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
@@ -56,6 +48,7 @@ public final class ProfileAvatarView: UIView {
}
extension ProfileAvatarView {
+
private func _init() {
backgroundColor = .clear
avatarContainerView.backgroundColor = .systemBackground
@@ -63,36 +56,10 @@ extension ProfileAvatarView {
avatarContainerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(avatarContainerView)
NSLayoutConstraint.activate([
- avatarContainerView.topAnchor.constraint(equalTo: topAnchor),
- avatarContainerView.leadingAnchor.constraint(equalTo: leadingAnchor),
- avatarContainerView.trailingAnchor.constraint(equalTo: trailingAnchor),
- avatarContainerView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ avatarContainerView.centerXAnchor.constraint(equalTo: centerXAnchor),
+ avatarContainerView.centerYAnchor.constraint(equalTo: centerYAnchor),
])
- avatarButton.translatesAutoresizingMaskIntoConstraints = false
- avatarContainerView.addSubview(avatarButton)
- avatarButtonWidthLayoutConstraint = avatarButton.widthAnchor.constraint(equalToConstant: ProfileAvatarView.primitiveAvatarButtonSize.width).priority(.required - 1)
- avatarButtonHeightLayoutConstraint = avatarButton.heightAnchor.constraint(equalToConstant: ProfileAvatarView.primitiveAvatarButtonSize.height).priority(.required - 1)
- NSLayoutConstraint.activate([
- avatarButton.topAnchor.constraint(equalTo: avatarContainerView.topAnchor),
- avatarButton.leadingAnchor.constraint(equalTo: avatarContainerView.leadingAnchor),
- avatarContainerView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor),
- avatarContainerView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor),
- avatarButtonWidthLayoutConstraint,
- avatarButtonHeightLayoutConstraint,
- ])
-
- badgeImageView.translatesAutoresizingMaskIntoConstraints = false
- addSubview(badgeImageView)
- badgeImageViewWidthLayoutConstraint = badgeImageView.widthAnchor.constraint(equalToConstant: ProfileAvatarView.primitiveBadgeImageViewSize.width).priority(.required - 1)
- badgeImageViewHeightLayoutConstraint = badgeImageView.heightAnchor.constraint(equalToConstant: ProfileAvatarView.primitiveBadgeImageViewSize.height).priority(.required - 1)
- NSLayoutConstraint.activate([
- badgeImageView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor),
- badgeImageView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor),
- badgeImageViewWidthLayoutConstraint,
- badgeImageViewHeightLayoutConstraint,
- ])
-
Publishers.CombineLatest3(
$avatarStyle,
$badge,
@@ -104,40 +71,156 @@ extension ProfileAvatarView {
}
.store(in: &disposeBag)
- updateScale()
+ setNeedsUpdateConstraints()
}
public override func layoutSubviews() {
super.layoutSubviews()
+ setNeedsUpdateConstraints()
layoutDidChange.send()
}
+ public override func updateConstraints() {
+ super.updateConstraints()
+
+ // assert width equalt to height
+
+ guard let dimension = self.dimension else { return }
+ self.avatarButtonWidthLayoutConstraint.constant = bounds.width
+ self.avatarButtonHeightLayoutConstraint.constant = bounds.height
+
+ let scale = bounds.width / dimension.primitiveAvatarButtonSize.width
+ let badgeDimension = dimension.primitiveBadgeImageViewSize.width * scale
+ self.badgeImageViewWidthLayoutConstraint.constant = badgeDimension
+ self.badgeImageViewHeightLayoutConstraint.constant = badgeDimension
+
+ self.setNeedsLayout()
+ }
+
+ public func setup(dimension: Dimension) {
+ guard self.dimension == nil else {
+ assertionFailure("only setup once")
+ return
+ }
+
+ self.dimension = dimension
+ dimension.layout(view: self)
+ setNeedsLayout()
+ }
+
}
extension ProfileAvatarView {
+
+ public enum Dimension {
+ case inline
+ case plain
+
+ var primitiveAvatarButtonSize: CGSize {
+ switch self {
+ case .inline: return CGSize(width: 44, height: 44)
+ case .plain: return CGSize(width: 88, height: 88)
+ }
+ }
+
+ var primitiveBadgeImageViewSize: CGSize {
+ return CGSize(width: 24, height: 24)
+ }
+
+ func layout(view: ProfileAvatarView) {
+ switch self {
+ case .inline: layoutInline(view: view)
+ case .plain: layoutPlain(view: view)
+ }
+ }
+ }
+
+}
- func updateScale() {
- self.avatarButtonWidthLayoutConstraint.constant = dimension
- self.avatarButtonHeightLayoutConstraint.constant = dimension
+extension ProfileAvatarView.Dimension {
+ func layoutInline(view: ProfileAvatarView) {
+ guard let dimension = view.dimension else {
+ assertionFailure()
+ return
+ }
- let scale = dimension / ProfileAvatarView.primitiveAvatarButtonSize.width
- let badgeDimension = ProfileAvatarView.primitiveBadgeImageViewSize.width * scale
- self.badgeImageViewWidthLayoutConstraint.constant = badgeDimension
- self.badgeImageViewHeightLayoutConstraint.constant = badgeDimension
+ view.avatarButton.translatesAutoresizingMaskIntoConstraints = false
+ view.avatarContainerView.addSubview(view.avatarButton)
+ view.avatarButtonWidthLayoutConstraint = view.avatarButton.widthAnchor.constraint(equalToConstant: dimension.primitiveAvatarButtonSize.width).priority(.required - 1)
+ view.avatarButtonHeightLayoutConstraint = view.avatarButton.heightAnchor.constraint(equalToConstant: dimension.primitiveAvatarButtonSize.height).priority(.required - 1)
+ NSLayoutConstraint.activate([
+ view.avatarButton.topAnchor.constraint(equalTo: view.avatarContainerView.topAnchor),
+ view.avatarButton.leadingAnchor.constraint(equalTo: view.avatarContainerView.leadingAnchor),
+ view.avatarContainerView.trailingAnchor.constraint(equalTo: view.avatarButton.trailingAnchor),
+ view.avatarContainerView.bottomAnchor.constraint(equalTo: view.avatarButton.bottomAnchor),
+ view.avatarButtonWidthLayoutConstraint,
+ view.avatarButtonHeightLayoutConstraint,
+ ])
- self.setNeedsLayout()
+ view.badgeImageView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(view.badgeImageView)
+ view.badgeImageViewWidthLayoutConstraint = view.badgeImageView.widthAnchor.constraint(equalToConstant: dimension.primitiveBadgeImageViewSize.width).priority(.required - 1)
+ view.badgeImageViewHeightLayoutConstraint = view.badgeImageView.heightAnchor.constraint(equalToConstant: dimension.primitiveBadgeImageViewSize.height).priority(.required - 1)
+ NSLayoutConstraint.activate([
+ view.badgeImageView.trailingAnchor.constraint(equalTo: view.avatarButton.trailingAnchor, constant: 4), // offset 4pt
+ view.badgeImageView.bottomAnchor.constraint(equalTo: view.avatarButton.bottomAnchor, constant: 2), // offset 2pt
+ view.badgeImageViewWidthLayoutConstraint,
+ view.badgeImageViewHeightLayoutConstraint,
+ ])
}
+ func layoutPlain(view: ProfileAvatarView) {
+ guard let dimension = view.dimension else {
+ assertionFailure()
+ return
+ }
+
+ view.avatarButton.translatesAutoresizingMaskIntoConstraints = false
+ view.avatarContainerView.addSubview(view.avatarButton)
+ view.avatarButtonWidthLayoutConstraint = view.avatarButton.widthAnchor.constraint(equalToConstant: dimension.primitiveAvatarButtonSize.width).priority(.required - 1)
+ view.avatarButtonHeightLayoutConstraint = view.avatarButton.heightAnchor.constraint(equalToConstant: dimension.primitiveAvatarButtonSize.height).priority(.required - 1)
+ NSLayoutConstraint.activate([
+ view.avatarButton.topAnchor.constraint(equalTo: view.avatarContainerView.topAnchor),
+ view.avatarButton.leadingAnchor.constraint(equalTo: view.avatarContainerView.leadingAnchor),
+ view.avatarContainerView.trailingAnchor.constraint(equalTo: view.avatarButton.trailingAnchor),
+ view.avatarContainerView.bottomAnchor.constraint(equalTo: view.avatarButton.bottomAnchor),
+ view.avatarButtonWidthLayoutConstraint,
+ view.avatarButtonHeightLayoutConstraint,
+ ])
+
+ view.badgeImageView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(view.badgeImageView)
+ view.badgeImageViewWidthLayoutConstraint = view.badgeImageView.widthAnchor.constraint(equalToConstant: dimension.primitiveBadgeImageViewSize.width).priority(.required - 1)
+ view.badgeImageViewHeightLayoutConstraint = view.badgeImageView.heightAnchor.constraint(equalToConstant: dimension.primitiveBadgeImageViewSize.height).priority(.required - 1)
+ NSLayoutConstraint.activate([
+ view.badgeImageView.trailingAnchor.constraint(equalTo: view.avatarButton.trailingAnchor),
+ view.badgeImageView.bottomAnchor.constraint(equalTo: view.avatarButton.bottomAnchor),
+ view.badgeImageViewWidthLayoutConstraint,
+ view.badgeImageViewHeightLayoutConstraint,
+ ])
+ }
}
extension ProfileAvatarView {
public enum Badge {
case none
- case circle
+ case circle(CircleBadge)
case robot
case verified
+
+ public enum CircleBadge {
+ case twitter
+ case mastodon
+
+ var image: UIImage {
+ switch self {
+ case .twitter: return Asset.Badge.circleTwitter.image
+ case .mastodon: return Asset.Badge.circleMastodon.image
+ }
+ }
+ }
}
public enum CornerStyle {
@@ -146,19 +229,28 @@ extension ProfileAvatarView {
}
private func updateBadge() {
+ guard let dimension = self.dimension else { return }
+
// mask avatarButton
let _avatarMaskImage: UIImage? = {
- switch badge {
- case .none:
+ switch (dimension, badge) {
+ case (_, .none):
return nil
- case .circle:
- return Asset.Badge.circleMask.image
- case .robot:
- return Asset.Badge.robotMask.image
- case .verified:
- return Asset.Badge.verifiedMask.image
+ case (.inline, .circle):
+ return Asset.Badge.circleMask44.image
+ case (.plain, .circle):
+ return Asset.Badge.circleMask88.image
+ case (.inline, .robot):
+ return Asset.Badge.robotMask44.image
+ case (.plain, .robot):
+ return Asset.Badge.robotMask88.image
+ case (.inline, .verified):
+ return Asset.Badge.verifiedMask44.image
+ case (.plain, .verified):
+ return Asset.Badge.verifiedMask88.image
}
}()
+
if let avatarMaskImage = _avatarMaskImage {
let frame = avatarButton.layer.frame
let imageView = UIImageView(frame: frame)
@@ -173,8 +265,8 @@ extension ProfileAvatarView {
switch badge {
case .none:
badgeImageView.image = nil
- case .circle:
- break
+ case .circle(let circleBadge):
+ badgeImageView.image = circleBadge.image
case .verified:
badgeImageView.image = Asset.Badge.verified.image
case .robot:
diff --git a/TwidereSDK/Sources/TwidereUI/Content/MediaView.swift b/TwidereSDK/Sources/TwidereUI/Content/MediaView.swift
index 4a9b6a34..4556dd5c 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/MediaView.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/MediaView.swift
@@ -89,7 +89,7 @@ extension MediaView {
private func _init() {
// lazy load content later
- isAccessibilityElement = true
+ imageView.isAccessibilityElement = true
}
public func setup(configuration: Configuration) {
diff --git a/TwidereSDK/Sources/TwidereUI/Content/PollOptionView+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Content/PollOptionView+ViewModel.swift
index cdd33d61..cfc1a8b3 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/PollOptionView+ViewModel.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/PollOptionView+ViewModel.swift
@@ -7,6 +7,7 @@
import UIKit
import Combine
+import CoreData
import CoreDataStack
import TwidereAsset
import TwitterMeta
@@ -27,6 +28,9 @@ extension PollOptionView {
public final class ViewModel: ObservableObject {
var disposeBag = Set()
var observations = Set()
+ var objects = Set()
+
+ @Published var authenticationContext: AuthenticationContext?
@Published var style: PollOptionView.Style?
@@ -47,6 +51,8 @@ extension PollOptionView {
@Published public var selectImageTintColor: UIColor = Asset.Colors.hightLight.color
@Published public var isReveal: Bool = false
+ @Published public var groupedAccessibilityLabel = ""
+
init() {
// corner
$isMultiple
@@ -98,6 +104,28 @@ extension PollOptionView {
return isExpire || isPollVoted || isMyPoll
}
.assign(to: &$isReveal)
+ // groupedAccessibilityLabel
+
+ Publishers.CombineLatest3(
+ $metaContent,
+ $percentage,
+ $isReveal
+ )
+ .map { metaContent, percentage, isReveal -> String in
+ var strings: [String?] = []
+
+ metaContent.flatMap { strings.append($0.string) }
+
+ if isReveal,
+ let percentage = percentage,
+ let string = PollOptionView.percentageFormatter.string(from: NSNumber(value: percentage))
+ {
+ strings.append(string)
+ }
+
+ return strings.compactMap { $0 }.joined(separator: ", ")
+ }
+ .assign(to: &$groupedAccessibilityLabel)
}
public enum Corner: Hashable {
@@ -208,22 +236,26 @@ extension PollOptionView.ViewModel {
$selectImageTintColor
.assign(to: \.tintColor, on: view.selectionImageView)
.store(in: &disposeBag)
+ // accessibility
+ $isSelect
+ .sink { isSelect in
+ if isSelect == true {
+ view.accessibilityTraits.insert(.selected)
+ } else {
+ view.accessibilityTraits.remove(.selected)
+ }
+ }
+ .store(in: &disposeBag)
+ $groupedAccessibilityLabel
+ .sink { groupedAccessibilityLabel in
+ view.accessibilityLabel = groupedAccessibilityLabel
+ }
+ .store(in: &disposeBag)
}
}
extension PollOptionView {
- public struct ConfigurationContext {
- public let dateTimeProvider: DateTimeProvider
- public let activeAuthenticationContext: AnyPublisher
-
- public init(
- dateTimeProvider: DateTimeProvider,
- activeAuthenticationContext: AnyPublisher
- ) {
- self.dateTimeProvider = dateTimeProvider
- self.activeAuthenticationContext = activeAuthenticationContext
- }
- }
+ public typealias ConfigurationContext = StatusView.ConfigurationContext
}
extension PollOptionView {
@@ -231,6 +263,12 @@ extension PollOptionView {
pollOption option: MastodonPollOption,
configurationContext: ConfigurationContext
) {
+ viewModel.objects.insert(option)
+
+ configurationContext.authenticationContext
+ .assign(to: \.authenticationContext, on: viewModel)
+ .store(in: &disposeBag)
+
// metaContent
Publishers.CombineLatest(
option.poll.status.publisher(for: \.emojis),
@@ -264,14 +302,14 @@ extension PollOptionView {
option.publisher(for: \.poll),
option.publisher(for: \.voteBy),
option.publisher(for: \.isSelected),
- configurationContext.activeAuthenticationContext
+ viewModel.$authenticationContext
)
- .sink { [weak self] poll, optionVoteBy, isSelected, activeAuthenticationContext in
+ .sink { [weak self] poll, optionVoteBy, isSelected, authenticationContext in
guard let self = self else { return }
let domain: String
let userID: String
- switch activeAuthenticationContext {
+ switch authenticationContext {
case .twitter, .none:
domain = ""
userID = ""
diff --git a/TwidereSDK/Sources/TwidereUI/Content/PollOptionView.swift b/TwidereSDK/Sources/TwidereUI/Content/PollOptionView.swift
index c6004ac4..9126c33b 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/PollOptionView.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/PollOptionView.swift
@@ -54,6 +54,7 @@ public final class PollOptionView: UIView {
}()
public func prepareForReuse() {
+ viewModel.objects.removeAll()
viewModel.percentage = nil
stripProgressView.setProgress(0, animated: false)
}
@@ -74,6 +75,10 @@ extension PollOptionView {
private func _init() {
textField.deleteBackwardDelegate = self
+
+ // Accessibility
+ // hint: Poll option
+ accessibilityHint = L10n.Accessibility.Common.Status.pollOptionOrdinalPrefix
}
public override func layoutSubviews() {
diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusMetricsDashboardView+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusMetricsDashboardView+ViewModel.swift
new file mode 100644
index 00000000..09320ed5
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereUI/Content/StatusMetricsDashboardView+ViewModel.swift
@@ -0,0 +1,85 @@
+//
+// StatusMetricsDashboardView+ViewModel.swift
+//
+//
+// Created by MainasuK on 2022-2-23.
+//
+
+import UIKit
+import Combine
+import CoreDataStack
+
+extension StatusMetricsDashboardView {
+ public final class ViewModel: ObservableObject {
+ var disposeBag = Set()
+
+ @Published public var platform: Platform = .none
+
+ @Published public var replyCount: Int = 0
+ @Published public var repostCount: Int = 0
+ @Published public var quoteCount: Int = 0
+ @Published public var likeCount: Int = 0
+
+ func bind(view: StatusMetricsDashboardView) {
+ // reply
+ $replyCount
+ .sink { count in
+ let text = ViewModel.metricText(count: count)
+ view.replyButton.setTitle(text, for: .normal)
+ view.replyButton.accessibilityLabel = L10n.Count.reply(count)
+ }
+ .store(in: &disposeBag)
+ // repost
+ Publishers.CombineLatest(
+ $repostCount,
+ $platform
+ )
+ .sink { count, platform in
+ let text = ViewModel.metricText(count: count)
+ view.repostButton.setTitle(text, for: .normal)
+
+ switch platform {
+ case .none:
+ view.repostButton.accessibilityLabel = nil
+ case .twitter:
+ view.repostButton.accessibilityLabel = L10n.Count.retweet(count)
+ case .mastodon:
+ view.repostButton.accessibilityLabel = L10n.Count.reblog(count)
+ }
+ }
+ .store(in: &disposeBag)
+ // quote
+ Publishers.CombineLatest(
+ $quoteCount,
+ $platform
+ )
+ .sink { count, platform in
+ let text = ViewModel.metricText(count: count)
+ view.quoteButton.setTitle(text, for: .normal)
+ view.quoteButton.accessibilityLabel = L10n.Count.quote(count)
+
+ view.quoteButton.isHidden = {
+ switch platform {
+ case .none: return true
+ case .twitter: return false
+ case .mastodon: return true
+ }
+ }()
+ }
+ .store(in: &disposeBag)
+ // like
+ $likeCount
+ .sink { count in
+ let text = ViewModel.metricText(count: count)
+ view.likeButton.setTitle(text, for: .normal)
+ view.likeButton.accessibilityLabel = L10n.Count.like(count)
+ }
+ .store(in: &disposeBag)
+ }
+
+ private static func metricText(count: Int) -> String {
+ guard count >= 0 else { return "0" }
+ return StatusMetricsDashboardView.numberMetricFormatter.string(from: count) ?? "0"
+ }
+ }
+}
diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusMetricsDashboardView.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusMetricsDashboardView.swift
index 2a9a6510..80825a70 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/StatusMetricsDashboardView.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/StatusMetricsDashboardView.swift
@@ -7,6 +7,7 @@
import os.log
import UIKit
+import CoreDataStack
import TwidereCommon
import TwidereCore
@@ -22,6 +23,12 @@ public final class StatusMetricsDashboardView: UIView {
public weak var delegate: StatusMetricsDashboardViewDelegate?
+ public private(set) lazy var viewModel: ViewModel = {
+ let viewModel = ViewModel()
+ viewModel.bind(view: self)
+ return viewModel
+ }()
+
public let container = UIStackView()
public let metaContainer = UIStackView()
@@ -36,6 +43,7 @@ public final class StatusMetricsDashboardView: UIView {
label.textAlignment = .center
return label
}()
+
public let dashboardContainer = UIStackView()
public let replyButton = HitTestExpandedButton()
@@ -76,8 +84,8 @@ extension StatusMetricsDashboardView {
metaContainer.addArrangedSubview(sourceLabel)
dashboardContainer.axis = .horizontal
- dashboardContainer.spacing = 8
- dashboardContainer.distribution = .fillEqually
+ dashboardContainer.spacing = 20
+ dashboardContainer.distribution = .equalCentering
container.addArrangedSubview(dashboardContainer)
let buttons = [replyButton, repostButton, quoteButton, likeButton]
@@ -88,15 +96,16 @@ extension StatusMetricsDashboardView {
button.setTitleColor(.secondaryLabel, for: .normal)
button.setInsets(forContentPadding: .zero, imageTitlePadding: 4)
button.addTarget(self, action: #selector(StatusMetricsDashboardView.buttonDidPressed(_:)), for: .touchUpInside)
+
+ // TODO: coordinate to the user list
+ button.accessibilityTraits = .staticText
}
replyButton.setImage(Asset.Arrows.arrowTurnUpLeftMini.image.withRenderingMode(.alwaysTemplate), for: .normal)
repostButton.setImage(Asset.Media.repeatMini.image.withRenderingMode(.alwaysTemplate), for: .normal)
quoteButton.setImage(Asset.TextFormatting.textQuoteMini.image.withRenderingMode(.alwaysTemplate), for: .normal)
- likeButton.setImage(Asset.Health.heartMini.image.withRenderingMode(.alwaysTemplate), for: .normal)
+ likeButton.setImage(Asset.Health.heartFillMini.image.withRenderingMode(.alwaysTemplate), for: .normal)
- let leadingPadding = UIView()
- dashboardContainer.addArrangedSubview(leadingPadding)
replyButton.translatesAutoresizingMaskIntoConstraints = false
repostButton.translatesAutoresizingMaskIntoConstraints = false
quoteButton.translatesAutoresizingMaskIntoConstraints = false
@@ -105,9 +114,7 @@ extension StatusMetricsDashboardView {
dashboardContainer.addArrangedSubview(repostButton)
dashboardContainer.addArrangedSubview(quoteButton)
dashboardContainer.addArrangedSubview(likeButton)
- let trailingPadding = UIView()
- dashboardContainer.addArrangedSubview(trailingPadding)
-
+
NSLayoutConstraint.activate([
replyButton.heightAnchor.constraint(equalToConstant: 44).priority(.required - 10),
replyButton.heightAnchor.constraint(equalTo: repostButton.heightAnchor).priority(.defaultHigh),
@@ -150,38 +157,7 @@ extension StatusMetricsDashboardView {
}
extension StatusMetricsDashboardView {
-
- private func metricText(count: Int) -> String {
- guard count > 0 else { return "" }
- return StatusMetricsDashboardView.numberMetricFormatter.string(from: count) ?? ""
- }
- public func setupReply(count: Int) {
- let text = metricText(count: count)
- replyButton.setTitle(text, for: .normal)
- replyButton.isHidden = count == 0
-
- // FIXME: a11y
- }
-
- public func setupRepost(count: Int) {
- let text = metricText(count: count)
- repostButton.setTitle(text, for: .normal)
- repostButton.isHidden = count == 0
- }
-
- public func setupQuote(count: Int) {
- let text = metricText(count: count)
- quoteButton.setTitle(text, for: .normal)
- quoteButton.isHidden = count == 0
- }
-
- public func setupLike(count: Int) {
- let text = metricText(count: count)
- likeButton.setTitle(text, for: .normal)
- likeButton.isHidden = count == 0
- }
-
public func setDashboardDisplay() {
dashboardContainer.isHidden = false
}
diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift
index 097e12f0..2441f295 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift
@@ -20,6 +20,12 @@ import Meta
extension StatusView {
public final class ViewModel: ObservableObject {
+ static let pollOptionOrdinalNumberFormatter: NumberFormatter = {
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .ordinal
+ return formatter
+ }()
+
var disposeBag = Set()
var observations = Set()
var objects = Set()
@@ -27,27 +33,40 @@ extension StatusView {
let logger = Logger(subsystem: "StatusView", category: "ViewModel")
@Published public var platform: Platform = .none
+ @Published public var authenticationContext: AuthenticationContext? // me
+ @Published public var managedObjectContext: NSManagedObjectContext?
@Published public var header: Header = .none
+ @Published public var userIdentifier: UserIdentifier?
@Published public var authorAvatarImage: UIImage?
@Published public var authorAvatarImageURL: URL?
@Published public var authorName: MetaContent?
@Published public var authorUsername: String?
@Published public var protected: Bool = false
-
+
+ @Published public var isMyself = false
+
@Published public var spoilerContent: MetaContent?
+
@Published public var content: MetaContent?
+ @Published public var twitterTextProvider: TwitterTextProvider?
+
@Published public var mediaViewConfigurations: [MediaView.Configuration] = []
- @Published public var isContentReveal: Bool = true
+ @Published public var isContentSensitive: Bool = false
+ @Published public var isContentSensitiveToggled: Bool = false
+
+ @Published public var isContentReveal: Bool = false
+
@Published public var isMediaSensitive: Bool = false
@Published public var isMediaSensitiveToggled: Bool = false
- @Published public var isMediaReveal: Bool = false
-
+
@Published public var isMediaSensitiveSwitchable = false
+ @Published public var isMediaReveal: Bool = false
+ // poll input
@Published public var pollItems: [PollItem] = []
@Published public var isVotable: Bool = false
@Published public var isVoting: Bool = false
@@ -57,11 +76,17 @@ extension StatusView {
@Published public var expireAt: Date?
@Published public var expired: Bool = false
+ // poll output
+ @Published public var pollVoteDescription = ""
+ @Published public var pollCountdownDescription: String?
+
@Published public var location: String?
@Published public var source: String?
- @Published public var isRepost: Bool = false
- @Published public var isLike: Bool = false
+ @Published public var isRepost = false
+ @Published public var isRepostEnabled = true
+
+ @Published public var isLike = false
@Published public var replyCount: Int = 0
@Published public var repostCount: Int = 0
@@ -87,7 +112,7 @@ extension StatusView {
.share()
.eraseToAnyPublisher()
- public let contentRevealChangePublisher = PassthroughSubject()
+ // public let contentRevealChangePublisher = PassthroughSubject()
public enum Header {
case none
@@ -101,12 +126,76 @@ extension StatusView {
}
init() {
+ // isMyself
+ Publishers.CombineLatest(
+ $authenticationContext,
+ $userIdentifier
+ )
+ .map { authenticationContext, userIdentifier in
+ guard let authenticationContext = authenticationContext,
+ let userIdentifier = userIdentifier
+ else { return false }
+ return authenticationContext.userIdentifier == userIdentifier
+ }
+ .assign(to: &$isMyself)
+ // isContentSensitive
+ Publishers.CombineLatest(
+ $platform,
+ $spoilerContent
+ )
+ .map { platform, spoilerContent in
+ switch platform {
+ case .none: return false
+ case .twitter: return false
+ case .mastodon: return spoilerContent != nil
+ }
+ }
+ .assign(to: &$isContentSensitive)
+ // isContentReveal
+ Publishers.CombineLatest(
+ $isContentSensitive,
+ $isContentSensitiveToggled
+ )
+ .map { $0 ? $1 : !$1 }
+ .assign(to: &$isContentReveal)
+ // isMediaReveal
Publishers.CombineLatest(
$isMediaSensitive,
$isMediaSensitiveToggled
)
- .map { $1 ? !$0 : $0 }
+ .map { $0 ? $1 : !$1 }
.assign(to: &$isMediaReveal)
+ // isRepostEnabled
+ Publishers.CombineLatest4(
+ $platform,
+ $visibility,
+ $protected,
+ $isMyself
+ )
+ .map { platform, visibility, protected, isMyself in
+ switch platform {
+ case .none:
+ return true
+ case .twitter:
+ return isMyself ? true : !protected
+ case .mastodon:
+ if isMyself {
+ return true
+ }
+ switch visibility {
+ case .none:
+ return true
+ case .mastodon(let visibility):
+ switch visibility {
+ case .public, .unlisted:
+ return true
+ case .private, .direct, ._other:
+ return false
+ }
+ }
+ }
+ }
+ .assign(to: &$isRepostEnabled)
}
}
}
@@ -209,6 +298,9 @@ extension StatusView.ViewModel {
else { return }
statusView.visibilityImageView.image = image
+ statusView.visibilityImageView.accessibilityLabel = visibility.accessibilityLabel
+ statusView.visibilityImageView.accessibilityTraits = .staticText
+ statusView.visibilityImageView.isAccessibilityElement = true
statusView.setVisibilityDisplay()
}
.store(in: &disposeBag)
@@ -262,30 +354,23 @@ extension StatusView.ViewModel {
statusView.setSpoilerDisplay()
}
.store(in: &disposeBag)
-
- Publishers.CombineLatest(
- $isContentReveal,
- $spoilerContent
- )
- .receive(on: DispatchQueue.main)
- .sink { [weak self] isContentReveal, spoilerContent in
- guard let self = self else { return }
- guard spoilerContent != nil else {
- // ignore reveal state when no spoiler exists
- statusView.contentTextView.isHidden = false
- return
+ $isContentReveal
+ .sink { isContentReveal in
+ statusView.contentTextView.isHidden = !isContentReveal
+
+ let label = isContentReveal ? L10n.Accessibility.Common.Status.Actions.hideContent : L10n.Accessibility.Common.Status.Actions.revealContent
+ statusView.expandContentButton.accessibilityLabel = label
}
-
- statusView.contentTextView.isHidden = !isContentReveal
- self.contentRevealChangePublisher.send()
- }
- .store(in: &disposeBag)
+ .store(in: &disposeBag)
$source
.sink { source in
statusView.metricsDashboardView.sourceLabel.text = source ?? ""
}
.store(in: &disposeBag)
// dashboard
+ $platform
+ .assign(to: \.platform, on: statusView.metricsDashboardView.viewModel)
+ .store(in: &disposeBag)
Publishers.CombineLatest4(
$replyCount,
$repostCount,
@@ -295,15 +380,10 @@ extension StatusView.ViewModel {
.sink { replyCount, repostCount, quoteCount, likeCount in
switch statusView.style {
case .plain:
- statusView.setMetricsDisplay()
-
- statusView.metricsDashboardView.setupReply(count: replyCount)
- statusView.metricsDashboardView.setupRepost(count: repostCount)
- statusView.metricsDashboardView.setupQuote(count: quoteCount)
- statusView.metricsDashboardView.setupLike(count: likeCount)
-
- let needsDashboardDisplay = replyCount > 0 || repostCount > 0 || quoteCount > 0 || likeCount > 0
- statusView.metricsDashboardView.dashboardContainer.isHidden = !needsDashboardDisplay
+ statusView.metricsDashboardView.viewModel.replyCount = replyCount
+ statusView.metricsDashboardView.viewModel.repostCount = repostCount
+ statusView.metricsDashboardView.viewModel.quoteCount = quoteCount
+ statusView.metricsDashboardView.viewModel.likeCount = likeCount
default:
break
}
@@ -351,7 +431,7 @@ extension StatusView.ViewModel {
.store(in: &disposeBag)
$isMediaReveal
.sink { isMediaReveal in
- statusView.mediaGridContainerView.viewModel.isContentWarningOverlayDisplay = isMediaReveal
+ statusView.mediaGridContainerView.viewModel.isContentWarningOverlayDisplay = !isMediaReveal
}
.store(in: &disposeBag)
$isMediaSensitiveSwitchable
@@ -381,7 +461,7 @@ extension StatusView.ViewModel {
}
.store(in: &disposeBag)
// poll
- let pollVoteDescription = Publishers.CombineLatest(
+ Publishers.CombineLatest(
$voterCount,
$voteCount
)
@@ -394,7 +474,8 @@ extension StatusView.ViewModel {
}
return description
}
- let pollCountdownDescription = Publishers.CombineLatest3(
+ .assign(to: &$pollVoteDescription)
+ Publishers.CombineLatest3(
$expireAt,
$expired,
timestampUpdatePublisher.prepend(Date()).eraseToAnyPublisher()
@@ -412,9 +493,10 @@ extension StatusView.ViewModel {
return timeLeft
}
+ .assign(to: &$pollCountdownDescription)
Publishers.CombineLatest(
- pollVoteDescription,
- pollCountdownDescription
+ $pollVoteDescription,
+ $pollCountdownDescription
)
.sink { pollVoteDescription, pollCountdownDescription in
let description = [
@@ -422,9 +504,9 @@ extension StatusView.ViewModel {
pollCountdownDescription
]
.compactMap { $0 }
- .joined(separator: " · ")
- statusView.pollVoteDescriptionLabel.text = description
+ statusView.pollVoteDescriptionLabel.text = description.joined(separator: " · ")
+ statusView.pollVoteDescriptionLabel.accessibilityLabel = description.joined(separator: ", ")
}
.store(in: &disposeBag)
Publishers.CombineLatest(
@@ -451,42 +533,56 @@ extension StatusView.ViewModel {
private func bindLocation(statusView: StatusView) {
$location
.sink { location in
- guard let location = location, !location.isEmpty else { return }
+ guard let location = location, !location.isEmpty else {
+ statusView.locationLabel.isAccessibilityElement = false
+ return
+ }
+ statusView.locationLabel.isAccessibilityElement = true
+
if statusView.traitCollection.preferredContentSizeCategory > .extraLarge {
statusView.locationMapPinImageView.image = Asset.ObjectTools.mappin.image
} else {
statusView.locationMapPinImageView.image = Asset.ObjectTools.mappinMini.image
}
statusView.locationLabel.text = location
+ statusView.locationLabel.accessibilityLabel = location
+
statusView.setLocationDisplay()
}
.store(in: &disposeBag)
}
private func bindToolbar(statusView: StatusView) {
+ // platform
+ $platform
+ .assign(to: \.platform, on: statusView.toolbar.viewModel)
+ .store(in: &disposeBag)
+ // reply
$replyCount
.sink { count in
- statusView.toolbar.setupReply(count: count, isEnabled: true)
+ statusView.toolbar.setupReply(count: count, isEnabled: true) // TODO:
}
.store(in: &disposeBag)
+ // repost
Publishers.CombineLatest3(
- $isRepost,
$repostCount,
- $protected
+ $isRepost,
+ $isRepostEnabled
)
- .sink { isRepost, count, protected in
- statusView.toolbar.setupRepost(count: count, isRepost: isRepost, isLocked: protected)
+ .sink { count, isRepost, isEnabled in
+ statusView.toolbar.setupRepost(count: count, isEnabled: isEnabled, isHighlighted: isRepost)
}
.store(in: &disposeBag)
+ // like
Publishers.CombineLatest(
- $isLike,
- $likeCount
+ $likeCount,
+ $isLike
)
- .sink { isLike, count in
- statusView.toolbar.setupLike(count: count, isLike: isLike)
+ .sink { count, isLike in
+ statusView.toolbar.setupLike(count: count, isHighlighted: isLike)
}
.store(in: &disposeBag)
-
+ // menu
Publishers.CombineLatest3(
$sharePlaintextContent,
$shareStatusURL,
@@ -503,13 +599,11 @@ extension StatusView.ViewModel {
}
private func bindAccessibility(statusView: StatusView) {
- let contentAccessibilityLabel = Publishers.CombineLatest4(
+ let authorAccessibilityLabel = Publishers.CombineLatest(
$header,
- $authorName,
- $timeAgoStyleTimestamp,
- $content
+ $authorName
)
- .map { header, authorName, timestamp, content -> String? in
+ .map { header, authorName -> String? in
var strings: [String?] = []
switch header {
@@ -522,32 +616,145 @@ extension StatusView.ViewModel {
}
strings.append(authorName?.string)
+
+ return strings.compactMap { $0 }.joined(separator: ", ")
+ }
+
+ let metaAccessibilityLabel = Publishers.CombineLatest(
+ $timeAgoStyleTimestamp,
+ $visibility
+ )
+ .map { timestamp, visibility -> String? in
+ var strings: [String?] = []
+
+ strings.append(visibility?.accessibilityLabel)
strings.append(timestamp)
- strings.append(content?.string)
return strings.compactMap { $0 }.joined(separator: ", ")
}
- let meidaAccessibilityLabel = $mediaViewConfigurations
+ let contentAccessibilityLabel = Publishers.CombineLatest4(
+ $platform,
+ $isContentReveal,
+ $spoilerContent,
+ $content
+ )
+ .map { platform, isContentReveal, spoilerContent, content -> String? in
+ var strings: [String?] = []
+ switch platform {
+ case .none:
+ break
+ case .twitter:
+ strings.append(content?.string)
+ case .mastodon:
+ if let spoilerContent = spoilerContent?.string {
+ strings.append(L10n.Accessibility.Common.Status.contentWarning)
+ strings.append(spoilerContent)
+ }
+ if isContentReveal {
+ strings.append(content?.string)
+ }
+ }
+
+ return strings.compactMap { $0 }.joined(separator: ", ")
+ }
+
+ let mediaAccessibilityLabel = $mediaViewConfigurations
.map { configurations -> String? in
let count = configurations.count
- // TODO: i18n
- return count > 0 ? "\(count) media" : nil
+ return count > 0 ? L10n.Count.media(count) : nil
+ }
+
+ let toolbarAccessibilityLabel = Publishers.CombineLatest3(
+ $platform,
+ $isRepost,
+ $isLike
+ )
+ .map { platform, isRepost, isLike -> String? in
+ var strings: [String?] = []
+
+ switch platform {
+ case .none:
+ break
+ case .twitter:
+ if isRepost {
+ strings.append(L10n.Accessibility.Common.Status.retweeted)
+ }
+ case .mastodon:
+ if isRepost {
+ strings.append(L10n.Accessibility.Common.Status.boosted)
+ }
}
- // TODO: Toolbar
+ if isLike {
+ strings.append(L10n.Accessibility.Common.Status.liked)
+ }
+
+ return strings.compactMap { $0 }.joined(separator: ", ")
+ }
- Publishers.CombineLatest(
+ let pollAccessibilityLabel = Publishers.CombineLatest3(
+ $pollItems,
+ $pollVoteDescription,
+ $pollCountdownDescription
+ )
+ .map { items, pollVoteDescription, pollCountdownDescription -> String? in
+ guard !items.isEmpty else { return nil }
+ guard let managedObjectContext = self.managedObjectContext else { return nil }
+
+ var strings: [String?] = []
+
+ let ordinalPrefix = L10n.Accessibility.Common.Status.pollOptionOrdinalPrefix
+
+ for (i, item) in items.enumerated() {
+ switch item {
+ case .option(let record):
+ guard let option = record.object(in: managedObjectContext) else { continue }
+ let number = NSNumber(value: i + 1)
+ guard let ordinal = StatusView.ViewModel.pollOptionOrdinalNumberFormatter.string(from: number) else { break }
+ strings.append("\(ordinalPrefix), \(ordinal), \(option.title)")
+
+ if option.isSelected {
+ strings.append(L10n.Accessibility.VoiceOver.selected)
+ }
+ }
+ }
+
+ strings.append(pollVoteDescription)
+ pollCountdownDescription.flatMap { strings.append($0) }
+
+ return strings.compactMap { $0 }.joined(separator: ", ")
+ }
+
+ let groupOne = Publishers.CombineLatest4(
+ authorAccessibilityLabel,
+ metaAccessibilityLabel,
contentAccessibilityLabel,
- meidaAccessibilityLabel
+ mediaAccessibilityLabel
)
- .map { content, media in
- let group = [
- content,
- media
- ]
+ .map { a, b, c, d -> String? in
+ return [a, b, c, d]
+ .compactMap { $0 }
+ .joined(separator: ", ")
+ }
+
+ let groupTwo = Publishers.CombineLatest3(
+ pollAccessibilityLabel,
+ $location,
+ toolbarAccessibilityLabel
+ )
+ .map { a, b, c -> String? in
+ return [a, b, c]
+ .compactMap { $0 }
+ .joined(separator: ", ")
+ }
- return group
+ Publishers.CombineLatest(
+ groupOne,
+ groupTwo
+ )
+ .map { a, b -> String in
+ return [a, b]
.compactMap { $0 }
.joined(separator: ", ")
}
@@ -558,6 +765,14 @@ extension StatusView.ViewModel {
statusView.accessibilityLabel = accessibilityLabel
}
.store(in: &disposeBag)
+
+ // poll
+ $pollItems
+ .sink { items in
+ statusView.pollVoteDescriptionLabel.isAccessibilityElement = !items.isEmpty
+ statusView.pollVoteButton.isAccessibilityElement = !items.isEmpty
+ }
+ .store(in: &disposeBag)
}
}
@@ -566,22 +781,25 @@ extension StatusView {
public struct ConfigurationContext {
public let dateTimeProvider: DateTimeProvider
public let twitterTextProvider: TwitterTextProvider
- public let activeAuthenticationContext: AnyPublisher
+ public let authenticationContext: Published.Publisher
public init(
dateTimeProvider: DateTimeProvider,
twitterTextProvider: TwitterTextProvider,
- activeAuthenticationContext: AnyPublisher
+ authenticationContext: Published.Publisher
) {
self.dateTimeProvider = dateTimeProvider
self.twitterTextProvider = twitterTextProvider
- self.activeAuthenticationContext = activeAuthenticationContext
+ self.authenticationContext = authenticationContext
}
}
}
extension StatusView {
- public func configure(feed: Feed, configurationContext: ConfigurationContext) {
+ public func configure(
+ feed: Feed,
+ configurationContext: ConfigurationContext
+ ) {
switch feed.content {
case .none:
logger.log(level: .info, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Warning] feed content missing")
@@ -637,23 +855,20 @@ extension StatusView {
twitterStatus status: TwitterStatus,
configurationContext: ConfigurationContext
) {
+ viewModel.managedObjectContext = status.managedObjectContext
viewModel.objects.insert(status)
+ viewModel.platform = .twitter
+ viewModel.dateTimeProvider = configurationContext.dateTimeProvider
+ viewModel.twitterTextProvider = configurationContext.twitterTextProvider
+ configurationContext.authenticationContext.assign(to: \.authenticationContext, on: viewModel).store(in: &disposeBag)
+
configureHeader(twitterStatus: status)
- configureAuthor(
- twitterStatus: status,
- dateTimeProvider: configurationContext.dateTimeProvider
- )
- configureContent(
- twitterStatus: status,
- twitterTextProvider: configurationContext.twitterTextProvider
- )
+ configureAuthor(twitterStatus: status)
+ configureContent(twitterStatus: status)
configureMedia(twitterStatus: status)
configureLocation(twitterStatus: status)
- configureToolbar(
- twitterStatus: status,
- activeAuthenticationContext: configurationContext.activeAuthenticationContext
- )
+ configureToolbar(twitterStatus: status)
if let quote = status.quote {
quoteStatusView?.configure(
@@ -680,11 +895,16 @@ extension StatusView {
}
}
- private func configureAuthor(
- twitterStatus status: TwitterStatus,
- dateTimeProvider: DateTimeProvider
- ) {
+ private func configureAuthor(twitterStatus status: TwitterStatus) {
+ guard let dateTimeProvider = viewModel.dateTimeProvider else {
+ assertionFailure()
+ return
+ }
+
let author = (status.repost ?? status).author
+
+ viewModel.userIdentifier = .twitter(.init(id: author.id))
+
// author avatar
author.publisher(for: \.profileImageURL)
.map { _ in author.avatarImageURL() }
@@ -712,10 +932,12 @@ extension StatusView {
.store(in: &disposeBag)
}
- private func configureContent(
- twitterStatus status: TwitterStatus,
- twitterTextProvider: TwitterTextProvider
- ) {
+ private func configureContent(twitterStatus status: TwitterStatus) {
+ guard let twitterTextProvider = viewModel.twitterTextProvider else {
+ assertionFailure()
+ return
+ }
+
let status = status.repost ?? status
let content = TwitterContent(content: status.displayText)
let metaContent = TwitterMetaContent.convert(
@@ -723,8 +945,10 @@ extension StatusView {
urlMaximumLength: 20,
twitterTextProvider: twitterTextProvider
)
- viewModel.isContentReveal = true
viewModel.spoilerContent = nil
+ viewModel.isContentReveal = true
+ viewModel.isContentSensitive = false
+ viewModel.isContentSensitiveToggled = false
viewModel.content = metaContent
viewModel.sharePlaintextContent = status.displayText
viewModel.source = status.source
@@ -750,11 +974,9 @@ extension StatusView {
.store(in: &disposeBag)
}
- private func configureToolbar(
- twitterStatus status: TwitterStatus,
- activeAuthenticationContext: AnyPublisher
- ) {
+ private func configureToolbar(twitterStatus status: TwitterStatus) {
let status = status.repost ?? status
+
status.publisher(for: \.replyCount)
.map(Int.init)
.assign(to: \.replyCount, on: viewModel)
@@ -763,6 +985,10 @@ extension StatusView {
.map(Int.init)
.assign(to: \.repostCount, on: viewModel)
.store(in: &disposeBag)
+ status.publisher(for: \.quoteCount)
+ .map(Int.init)
+ .assign(to: \.quoteCount, on: viewModel)
+ .store(in: &disposeBag)
status.publisher(for: \.likeCount)
.map(Int.init)
.assign(to: \.likeCount, on: viewModel)
@@ -771,7 +997,7 @@ extension StatusView {
// relationship
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.repostBy)
)
.map { authenticationContext, repostBy in
@@ -785,7 +1011,7 @@ extension StatusView {
.store(in: &disposeBag)
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.likeBy)
)
.map { authenticationContext, likeBy in
@@ -799,7 +1025,7 @@ extension StatusView {
.store(in: &disposeBag)
let authorUserID = status.author.id
- activeAuthenticationContext
+ viewModel.$authenticationContext
.map { authenticationContext in
guard let authenticationContext = authenticationContext?.twitterAuthenticationContext else {
return false
@@ -818,14 +1044,20 @@ extension StatusView {
notification: MastodonNotification?,
configurationContext: ConfigurationContext
) {
+ viewModel.managedObjectContext = status.managedObjectContext
viewModel.objects.insert(status)
+ viewModel.platform = .mastodon
+ viewModel.dateTimeProvider = configurationContext.dateTimeProvider
+ viewModel.twitterTextProvider = configurationContext.twitterTextProvider
+ configurationContext.authenticationContext.assign(to: \.authenticationContext, on: viewModel).store(in: &disposeBag)
+
configureHeader(mastodonStatus: status, mastodonNotification: notification)
- configureAuthor(mastodonStatus: status, dateTimeProvider: configurationContext.dateTimeProvider)
+ configureAuthor(mastodonStatus: status)
configureContent(mastodonStatus: status)
configureMedia(mastodonStatus: status)
- configurePoll(mastodonStatus: status, activeAuthenticationContext: configurationContext.activeAuthenticationContext)
- configureToolbar(mastodonStatus: status, activeAuthenticationContext: configurationContext.activeAuthenticationContext)
+ configurePoll(mastodonStatus: status)
+ configureToolbar(mastodonStatus: status)
}
private func configureHeader(
@@ -872,11 +1104,11 @@ extension StatusView {
}
}
- private func configureAuthor(
- mastodonStatus status: MastodonStatus,
- dateTimeProvider: DateTimeProvider
- ) {
+ private func configureAuthor(mastodonStatus status: MastodonStatus) {
let author = (status.repost ?? status).author
+
+ viewModel.userIdentifier = .mastodon(.init(domain: author.domain, id: author.id))
+
// author avatar
author.publisher(for: \.avatar)
.map { url in url.flatMap { URL(string: $0) } }
@@ -911,7 +1143,6 @@ extension StatusView {
// visibility
viewModel.visibility = status.visibility.asStatusVisibility
// timestamp
- viewModel.dateTimeProvider = dateTimeProvider
(status.repost ?? status).publisher(for: \.createdAt)
.map { $0 as Date? }
.assign(to: \.timestamp, on: viewModel)
@@ -943,8 +1174,9 @@ extension StatusView {
viewModel.spoilerContent = nil
}
- status.publisher(for: \.isContentReveal)
- .assign(to: \.isContentReveal, on: viewModel)
+ viewModel.isContentSensitiveToggled = status.isContentSensitiveToggled
+ status.publisher(for: \.isContentSensitiveToggled)
+ .assign(to: \.isContentSensitiveToggled, on: viewModel)
.store(in: &disposeBag)
viewModel.source = status.source
@@ -968,21 +1200,13 @@ extension StatusView {
animated: false
)
- status.publisher(for: \.isMediaSensitive)
- .receive(on: DispatchQueue.main)
- .assign(to: \.isMediaSensitive, on: viewModel)
- .store(in: &disposeBag)
-
status.publisher(for: \.isMediaSensitiveToggled)
.receive(on: DispatchQueue.main)
.assign(to: \.isMediaSensitiveToggled, on: viewModel)
.store(in: &disposeBag)
}
- private func configurePoll(
- mastodonStatus status: MastodonStatus,
- activeAuthenticationContext: AnyPublisher
- ) {
+ private func configurePoll(mastodonStatus status: MastodonStatus) {
let status = status.repost ?? status
// pollItems
@@ -1014,7 +1238,7 @@ extension StatusView {
Publishers.CombineLatest3(
poll.publisher(for: \.voteBy),
poll.publisher(for: \.expired),
- activeAuthenticationContext
+ viewModel.$authenticationContext
)
.map { voteBy, expired, authenticationContext in
guard case let .mastodon(authenticationContext) = authenticationContext else { return false }
@@ -1049,12 +1273,10 @@ extension StatusView {
.store(in: &disposeBag)
}
- private func configureToolbar(
- mastodonStatus status: MastodonStatus,
- activeAuthenticationContext: AnyPublisher
- ) {
+ private func configureToolbar(mastodonStatus status: MastodonStatus) {
let status = status.repost ?? status
+ viewModel.quoteCount = 0
status.publisher(for: \.replyCount)
.map(Int.init)
.assign(to: \.replyCount, on: viewModel)
@@ -1071,7 +1293,7 @@ extension StatusView {
// relationship
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.repostBy)
)
.map { authenticationContext, repostBy in
@@ -1086,7 +1308,7 @@ extension StatusView {
.store(in: &disposeBag)
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.likeBy)
)
.map { authenticationContext, likeBy in
@@ -1101,7 +1323,7 @@ extension StatusView {
.store(in: &disposeBag)
let authorUserID = status.author.id
- activeAuthenticationContext
+ viewModel.$authenticationContext
.map { authenticationContext in
guard let authenticationContext = authenticationContext?.mastodonAuthenticationContext else {
return false
diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift
index 94f4e38a..c6107955 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift
@@ -52,7 +52,7 @@ public final class StatusView: UIView {
public static let bodyContainerStackViewSpacing: CGFloat = 10
public static let quoteStatusViewContainerLayoutMargin: CGFloat = 12
- let logger = Logger(subsystem: "StatusView", category: "UI")
+ let logger = Logger(subsystem: "StatusView", category: "View")
public private(set) var style: Style?
@@ -74,14 +74,22 @@ public final class StatusView: UIView {
public let headerContainerView = UIView()
public let headerIconImageView = UIImageView()
public static var headerTextLabelStyle: TextStyle { .statusHeader }
- public let headerTextLabel = MetaLabel(style: .statusHeader)
+ public let headerTextLabel: MetaLabel = {
+ let label = MetaLabel(style: .statusHeader)
+ label.isUserInteractionEnabled = false
+ return label
+ }()
// avatar
public let authorAvatarButton = AvatarButton()
// author
public static var authorNameLabelStyle: TextStyle { .statusAuthorName }
- public let authorNameLabel = MetaLabel(style: StatusView.authorNameLabelStyle)
+ public let authorNameLabel: MetaLabel = {
+ let label = MetaLabel(style: StatusView.authorNameLabelStyle)
+ label.accessibilityTraits = .staticText
+ return label
+ }()
public let lockImageView: UIImageView = {
let imageView = UIImageView()
imageView.tintColor = .secondaryLabel
@@ -257,12 +265,12 @@ extension StatusView {
])
// header
- headerTextLabel.isUserInteractionEnabled = false
let headerTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
headerTapGestureRecognizer.addTarget(self, action: #selector(StatusView.headerTapGestureRecognizerHandler(_:)))
headerContainerView.addGestureRecognizer(headerTapGestureRecognizer)
// avatar button
- authorAvatarButton.accessibilityLabel = "Open Author Profile" // TODO: i18n
+ authorAvatarButton.accessibilityLabel = L10n.Accessibility.Common.Status.authorAvatar
+ authorAvatarButton.accessibilityHint = L10n.Accessibility.VoiceOver.doubleTapToOpenProfile
authorAvatarButton.addTarget(self, action: #selector(StatusView.authorAvatarButtonDidPressed(_:)), for: .touchUpInside)
// expand content
expandContentButton.addTarget(self, action: #selector(StatusView.expandContentButtonDidPressed(_:)), for: .touchUpInside)
@@ -329,7 +337,6 @@ extension StatusView {
statusView.pollVoteActivityIndicatorView.isHidden = true
statusView.quoteStatusView?.isHidden = true
statusView.locationContainer.isHidden = true
- statusView.metricsDashboardView.isHidden = true
}
}
}
@@ -904,10 +911,6 @@ extension StatusView {
locationContainer.isHidden = false
}
- public func setMetricsDisplay() {
- metricsDashboardView.isHidden = false
- }
-
// content text Width
public var contentMaxLayoutWidth: CGFloat {
let inset = contentLayoutInset
diff --git a/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift
index 06f47e36..27679b6f 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift
@@ -31,7 +31,7 @@ extension UserView {
// TODO: verified | bot
@Published public var name: MetaContent? = PlaintextMetaContent(string: " ")
- @Published public var username: String? = " "
+ @Published public var username: String?
@Published public var protected: Bool = false
@@ -68,20 +68,19 @@ extension UserView.ViewModel {
case .none:
userView.authorProfileAvatarView.badge = .none
case .platform:
- let badge: UIImage? = {
+ userView.authorProfileAvatarView.badge = {
switch platform {
- case .none: return nil
- case .twitter: return Asset.Badge.circleTwitter.image
- case .mastodon: return Asset.Badge.circleMastodon.image
+ case .none: return .none
+ case .twitter: return .circle(.twitter)
+ case .mastodon: return .circle(.mastodon)
}
}()
- userView.authorProfileAvatarView.badge = badge != nil ? .circle : .none
- userView.authorProfileAvatarView.badgeImageView.image = badge
case .user:
userView.authorProfileAvatarView.badge = .none
}
}
.store(in: &disposeBag)
+ // badge
UserDefaults.shared
.observe(\.avatarStyle, options: [.initial, .new]) { defaults, _ in
let avatarStyle = defaults.avatarStyle
@@ -98,21 +97,6 @@ extension UserView.ViewModel {
animator.startAnimation()
}
.store(in: &observations)
-// // badge
-// $platform
-// .sink { platform in
-// switch platform {
-// case .twitter:
-// userView.badgeImageView.image = Asset.Badge.twitter.image
-// userView.setBadgeDisplay()
-// case .mastodon:
-// userView.badgeImageView.image = Asset.Badge.mastodon.image
-// userView.setBadgeDisplay()
-// case .none:
-// break
-// }
-// }
-// .store(in: &disposeBag)
// header
$header
.sink { header in
@@ -140,6 +124,9 @@ extension UserView.ViewModel {
.store(in: &disposeBag)
// username
$username
+ .map { username in
+ return username.flatMap { "@\($0)" } ?? " "
+ }
.assign(to: \.text, on: userView.usernameLabel)
.store(in: &disposeBag)
// protected
@@ -235,7 +222,7 @@ extension UserView {
.store(in: &disposeBag)
// author username
user.publisher(for: \.username)
- .map { "@\($0)" as String? }
+ .map { $0 as String? }
.assign(to: \.username, on: viewModel)
.store(in: &disposeBag)
// protected
@@ -276,8 +263,8 @@ extension UserView {
.assign(to: \.name, on: viewModel)
.store(in: &disposeBag)
// author username
- user.publisher(for: \.username)
- .map { "@\($0)" as String? }
+ user.publisher(for: \.acct)
+ .map { _ in user.acctWithDomain as String? }
.assign(to: \.username, on: viewModel)
.store(in: &disposeBag)
// protected
diff --git a/TwidereSDK/Sources/TwidereUI/Content/UserView.swift b/TwidereSDK/Sources/TwidereUI/Content/UserView.swift
index f059a81d..73bd1593 100644
--- a/TwidereSDK/Sources/TwidereUI/Content/UserView.swift
+++ b/TwidereSDK/Sources/TwidereUI/Content/UserView.swift
@@ -72,7 +72,11 @@ public final class UserView: UIView {
public let headerTextLabel = MetaLabel(style: .statusHeader)
// avatar
- public let authorProfileAvatarView = ProfileAvatarView()
+ public let authorProfileAvatarView: ProfileAvatarView = {
+ let profileAvatarView = ProfileAvatarView()
+ profileAvatarView.setup(dimension: .inline)
+ return profileAvatarView
+ }()
// name
public let nameLabel = MetaLabel(style: .userAuthorName)
@@ -166,8 +170,12 @@ extension UserView {
containerStackView.addArrangedSubview(contentStackView)
// content: H - [ user avatar | info container | accessory container ]
- authorProfileAvatarView.dimension = UserView.avatarImageViewSize.width
+ authorProfileAvatarView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.addArrangedSubview(authorProfileAvatarView)
+ NSLayoutConstraint.activate([
+ authorProfileAvatarView.widthAnchor.constraint(equalToConstant: UserView.avatarImageViewSize.width).priority(.required - 1),
+ authorProfileAvatarView.heightAnchor.constraint(equalToConstant: UserView.avatarImageViewSize.height).priority(.required - 1),
+ ])
contentStackView.addArrangedSubview(infoContainerStackView)
contentStackView.addArrangedSubview(accessoryContainerView)
diff --git a/TwidereSDK/Sources/TwidereUI/TableViewCell/PollOptionTableViewCell.swift b/TwidereSDK/Sources/TwidereUI/TableViewCell/PollOptionTableViewCell.swift
index 9a3665fe..9e4078d9 100644
--- a/TwidereSDK/Sources/TwidereUI/TableViewCell/PollOptionTableViewCell.swift
+++ b/TwidereSDK/Sources/TwidereUI/TableViewCell/PollOptionTableViewCell.swift
@@ -54,7 +54,12 @@ extension PollOptionTableViewCell {
contentView.bottomAnchor.constraint(equalTo: optionView.bottomAnchor, constant: PollOptionTableViewCell.margin),
optionView.heightAnchor.constraint(equalToConstant: PollOptionView.height).priority(.required - 1),
])
- optionView.setup(style: .plain)
+ optionView.setup(style: .plain)
+
+ // accessibility
+ accessibilityElements = [optionView]
+ optionView.isAccessibilityElement = true
+
}
}
diff --git a/TwidereSDK/Sources/TwidereUI/TableViewCell/UserTableViewCell.swift b/TwidereSDK/Sources/TwidereUI/TableViewCell/UserTableViewCell.swift
index 1ce407a2..704b43c3 100644
--- a/TwidereSDK/Sources/TwidereUI/TableViewCell/UserTableViewCell.swift
+++ b/TwidereSDK/Sources/TwidereUI/TableViewCell/UserTableViewCell.swift
@@ -18,7 +18,7 @@ public class UserTableViewCell: UITableViewCell {
var disposeBag = Set()
- let logger = Logger(subsystem: "UserTableViewCell", category: "UI")
+ let logger = Logger(subsystem: "UserTableViewCell", category: "View")
public let userView = UserView()
diff --git a/TwidereSDK/Sources/TwidereUI/Toolbar/StatusToolbar+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Toolbar/StatusToolbar+ViewModel.swift
new file mode 100644
index 00000000..f581b33d
--- /dev/null
+++ b/TwidereSDK/Sources/TwidereUI/Toolbar/StatusToolbar+ViewModel.swift
@@ -0,0 +1,243 @@
+//
+// StatusToolbar+ViewModel.swift
+//
+//
+// Created by MainasuK on 2022-2-23.
+//
+
+import UIKit
+import Combine
+import CoreDataStack
+import TwidereAsset
+
+extension StatusToolbar {
+ public final class ViewModel: ObservableObject {
+ var disposeBag = Set()
+
+ @Published public var traitCollectionDidChange = CurrentValueSubject(Void())
+ @Published public var platform: Platform = .none
+
+ @Published public var replyCount: Int = 0
+ @Published public var isReplyEnabled = true
+
+ @Published public var repostCount: Int = 0
+ @Published public var isRepostEnabled = true
+ @Published public var isRepostHighlighted = true
+
+ @Published public var likeCount: Int = 0
+ // @Published public var isLikeEnabled = true
+ @Published public var isLikeHighlighted = true
+
+ func bind(view: StatusToolbar) {
+ // reply
+ Publishers.CombineLatest(
+ $replyCount,
+ $isReplyEnabled
+ )
+ .sink { count, isEnabled in
+ // title
+ let text = ViewModel.metricText(count: count)
+ switch view.style {
+ case .none:
+ break
+ case .inline:
+ view.replyButton.setTitle(text, for: .normal)
+ view.replyButton.accessibilityHint = L10n.Count.reply(count)
+ case .plain:
+ view.replyButton.accessibilityHint = nil
+ }
+ view.replyButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.reply
+
+ // isEnabled
+ view.replyButton.isEnabled = isEnabled
+ }
+ .store(in: &disposeBag)
+ // repost
+ Publishers.CombineLatest3(
+ $repostCount,
+ $isRepostEnabled,
+ $platform
+ )
+ .sink { count, isEnabled, platform in
+ // title
+ let text = ViewModel.metricText(count: count)
+ switch view.style {
+ case .none:
+ break
+ case .inline:
+ view.repostButton.setTitle(text, for: .normal)
+ view.repostButton.accessibilityHint = L10n.Count.reblog(count)
+ case .plain:
+ view.repostButton.accessibilityHint = nil
+ }
+
+ switch platform {
+ case .none:
+ view.repostButton.accessibilityLabel = nil
+ case .twitter:
+ view.repostButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.retweet
+ case .mastodon:
+ view.repostButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.boost
+ }
+
+ // isEnabled
+ view.repostButton.isEnabled = isEnabled
+ }
+ .store(in: &disposeBag)
+ Publishers.CombineLatest(
+ $isRepostHighlighted,
+ $traitCollectionDidChange
+ )
+ .sink { isHighlighted, _ in
+ // isHighlighted
+ let tintColor = isHighlighted ? Asset.Scene.Status.Toolbar.repost.color : .secondaryLabel
+ view.repostButton.tintColor = tintColor
+ view.repostButton.setTitleColor(tintColor, for: .normal)
+ view.repostButton.setTitleColor(tintColor.withAlphaComponent(0.8), for: .highlighted)
+ if isHighlighted {
+ view.repostButton.accessibilityTraits.insert(.selected)
+ } else {
+ view.repostButton.accessibilityTraits.remove(.selected)
+ }
+ }
+ .store(in: &disposeBag)
+ // like
+ $likeCount
+ .sink { count in
+ // title
+ let text = ViewModel.metricText(count: count)
+ switch view.style {
+ case .none:
+ break
+ case .inline:
+ view.likeButton.setTitle(text, for: .normal)
+ view.likeButton.accessibilityHint = L10n.Count.reply(count)
+ case .plain:
+ view.likeButton.accessibilityHint = nil
+ // no titile
+ }
+ view.likeButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.like
+ }
+ .store(in: &disposeBag)
+ Publishers.CombineLatest(
+ $isLikeHighlighted,
+ $traitCollectionDidChange
+ )
+ .sink { isHighlighted, _ in
+ // isHighlighted
+ let tintColor = isHighlighted ? Asset.Scene.Status.Toolbar.like.color : .secondaryLabel
+ view.likeButton.tintColor = tintColor
+ view.likeButton.setTitleColor(tintColor, for: .normal)
+ view.likeButton.setTitleColor(tintColor.withAlphaComponent(0.8), for: .highlighted)
+ switch view.style {
+ case .none:
+ break
+ case .inline:
+ let image: UIImage = isHighlighted ? Asset.Health.heartFillMini.image : Asset.Health.heartMini.image
+ view.likeButton.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
+ case .plain:
+ let image: UIImage = isHighlighted ? Asset.Health.heartFill.image : Asset.Health.heart.image
+ view.likeButton.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
+ }
+ if isHighlighted {
+ view.likeButton.accessibilityTraits.insert(.selected)
+ } else {
+ view.likeButton.accessibilityTraits.remove(.selected)
+ }
+ }
+ .store(in: &disposeBag)
+ }
+
+ private static func metricText(count: Int) -> String {
+ guard count > 0 else { return "" }
+ return StatusToolbar.numberMetricFormatter.string(from: count) ?? ""
+ }
+ }
+}
+
+extension StatusToolbar {
+
+ public func setupReply(count: Int, isEnabled: Bool) {
+ viewModel.replyCount = count
+ viewModel.isReplyEnabled = isEnabled
+ }
+
+ public func setupRepost(count: Int, isEnabled: Bool, isHighlighted: Bool) {
+ viewModel.repostCount = count
+ viewModel.isRepostEnabled = isEnabled
+ viewModel.isRepostHighlighted = isHighlighted
+ }
+
+ public func setupLike(count: Int, isHighlighted: Bool) {
+ viewModel.likeCount = count
+ viewModel.isLikeHighlighted = isHighlighted
+ }
+
+ public struct MenuContext {
+ let shareText: String?
+ let shareLink: String?
+ let displayDeleteAction: Bool
+ }
+
+ public func setupMenu(menuContext: MenuContext) {
+ menuButton.menu = {
+ var children: [UIMenuElement] = [
+ UIAction(
+ title: L10n.Common.Controls.Status.Actions.copyText.capitalized,
+ image: UIImage(systemName: "doc.on.doc"),
+ identifier: nil,
+ discoverabilityTitle: nil,
+ attributes: [],
+ state: .off
+ ) { _ in
+ guard let text = menuContext.shareText else { return }
+ UIPasteboard.general.string = text
+ },
+ UIAction(
+ title: L10n.Common.Controls.Status.Actions.copyLink.capitalized,
+ image: UIImage(systemName: "link"),
+ identifier: nil,
+ discoverabilityTitle: nil,
+ attributes: [],
+ state: .off
+ ) { _ in
+ guard let text = menuContext.shareLink else { return }
+ UIPasteboard.general.string = text
+ },
+ UIAction(
+ title: L10n.Common.Controls.Status.Actions.shareLink.capitalized,
+ image: UIImage(systemName: "square.and.arrow.up"),
+ identifier: nil,
+ discoverabilityTitle: nil,
+ attributes: [],
+ state: .off
+ ) { [weak self] _ in
+ guard let self = self else { return }
+ self.delegate?.statusToolbar(self, actionDidPressed: .menu, button: self.menuButton)
+ }
+ ]
+
+ if menuContext.displayDeleteAction {
+ let removeAction = UIAction(
+ title: L10n.Common.Controls.Actions.delete,
+ image: UIImage(systemName: "minus.circle"),
+ identifier: nil,
+ discoverabilityTitle: nil,
+ attributes: .destructive,
+ state: .off
+ ) { [weak self] _ in
+ guard let self = self else { return }
+ self.delegate?.statusToolbar(self, menuActionDidPressed: .remove, menuButton: self.menuButton)
+ }
+ children.append(removeAction)
+ }
+
+ return UIMenu(title: "", options: [], children: children)
+ }()
+
+
+ menuButton.showsMenuAsPrimaryAction = true
+ menuButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.menu
+ }
+
+}
diff --git a/TwidereSDK/Sources/TwidereUI/Toolbar/StatusToolbar.swift b/TwidereSDK/Sources/TwidereUI/Toolbar/StatusToolbar.swift
index 649c9a6b..a5ab601a 100644
--- a/TwidereSDK/Sources/TwidereUI/Toolbar/StatusToolbar.swift
+++ b/TwidereSDK/Sources/TwidereUI/Toolbar/StatusToolbar.swift
@@ -19,16 +19,23 @@ public final class StatusToolbar: UIView {
public static let numberMetricFormatter = NumberMetricFormatter()
+ public weak var delegate: StatusToolbarDelegate?
+
+ public private(set) lazy var viewModel: ViewModel = {
+ let viewModel = ViewModel()
+ viewModel.bind(view: self)
+ return viewModel
+ }()
+
+ private let logger = Logger(subsystem: "StatusToolbar", category: "Toolbar")
+ private let container = UIStackView()
+ private(set) var style: Style?
+
public let replyButton = HitTestExpandedButton()
public let repostButton = HitTestExpandedButton()
public let likeButton = HitTestExpandedButton()
public let menuButton = HitTestExpandedButton()
- private let logger = Logger(subsystem: "StatusToolbar", category: "UI")
- private let container = UIStackView()
- private var style: Style?
-
- public weak var delegate: StatusToolbarDelegate?
public override init(frame: CGRect) {
super.init(frame: frame)
@@ -58,6 +65,17 @@ extension StatusToolbar {
container.trailingAnchor.constraint(equalTo: trailingAnchor),
container.bottomAnchor.constraint(equalTo: bottomAnchor),
])
+
+ replyButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.reply
+ // dynamic label for repostButton
+ likeButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.like
+ menuButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.menu
+ }
+
+ public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ viewModel.traitCollectionDidChange.send()
}
public func setup(style: Style) {
@@ -185,151 +203,3 @@ extension StatusToolbar {
}
}
-
-extension StatusToolbar {
-
- private func metricText(count: Int) -> String {
- guard count > 0 else { return "" }
- return StatusToolbar.numberMetricFormatter.string(from: count) ?? ""
- }
-
- public func setupReply(count: Int, isEnabled: Bool) {
- let text = metricText(count: count)
- switch style {
- case .inline:
- replyButton.setTitle(text, for: .normal)
- case .plain:
- break
- case .none:
- break
- }
-
- replyButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.reply
- }
-
- public func setupRepost(count: Int, isRepost: Bool, isLocked: Bool) {
- // set title
- let text = metricText(count: count)
- switch style {
- case .inline:
- repostButton.setTitle(text, for: .normal)
- case .plain:
- break
- case .none:
- break
- }
-
- // set color
- let tintColor = isRepost ? Asset.Scene.Status.Toolbar.repost.color : .secondaryLabel
- repostButton.tintColor = tintColor
- repostButton.setTitleColor(tintColor, for: .normal)
- repostButton.setTitleColor(tintColor.withAlphaComponent(0.8), for: .highlighted)
-
- // TODO: loked
-
- repostButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.retweet
- if isRepost {
- repostButton.accessibilityTraits.insert(.selected)
- } else {
- repostButton.accessibilityTraits.remove(.selected)
- }
- }
-
- public func setupLike(count: Int, isLike: Bool) {
- // set title
- let text = metricText(count: count)
- switch style {
- case .inline:
- let image: UIImage = isLike ? Asset.Health.heartFillMini.image : Asset.Health.heartMini.image
- likeButton.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
- likeButton.setTitle(text, for: .normal)
- case .plain:
- let image: UIImage = isLike ? Asset.Health.heartFill.image : Asset.Health.heart.image
- likeButton.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
- // no title
- case .none:
- break
- }
-
- // set color
- let tintColor = isLike ? Asset.Scene.Status.Toolbar.like.color : .secondaryLabel
- likeButton.tintColor = tintColor
- likeButton.setTitleColor(tintColor, for: .normal)
- likeButton.setTitleColor(tintColor.withAlphaComponent(0.8), for: .highlighted)
-
- likeButton.accessibilityLabel = L10n.Accessibility.Common.Status.Actions.like
- if isLike {
- likeButton.accessibilityTraits.insert(.selected)
- } else {
- likeButton.accessibilityTraits.remove(.selected)
- }
- }
-
- public struct MenuContext {
- let shareText: String?
- let shareLink: String?
- let displayDeleteAction: Bool
- }
-
- public func setupMenu(menuContext: MenuContext) {
- menuButton.menu = {
- var children: [UIMenuElement] = [
- UIAction(
- title: L10n.Common.Controls.Status.Actions.copyText.capitalized,
- image: UIImage(systemName: "doc.on.doc"),
- identifier: nil,
- discoverabilityTitle: nil,
- attributes: [],
- state: .off
- ) { _ in
- guard let text = menuContext.shareText else { return }
- UIPasteboard.general.string = text
- },
- UIAction(
- title: L10n.Common.Controls.Status.Actions.copyLink.capitalized,
- image: UIImage(systemName: "link"),
- identifier: nil,
- discoverabilityTitle: nil,
- attributes: [],
- state: .off
- ) { _ in
- guard let text = menuContext.shareLink else { return }
- UIPasteboard.general.string = text
- },
- UIAction(
- title: L10n.Common.Controls.Status.Actions.shareLink.capitalized,
- image: UIImage(systemName: "square.and.arrow.up"),
- identifier: nil,
- discoverabilityTitle: nil,
- attributes: [],
- state: .off
- ) { [weak self] _ in
- guard let self = self else { return }
- self.delegate?.statusToolbar(self, actionDidPressed: .menu, button: self.menuButton)
- }
- ]
-
- if menuContext.displayDeleteAction {
- let removeAction = UIAction(
- title: L10n.Common.Controls.Actions.delete,
- image: UIImage(systemName: "minus.circle"),
- identifier: nil,
- discoverabilityTitle: nil,
- attributes: .destructive,
- state: .off
- ) { [weak self] _ in
- guard let self = self else { return }
- self.delegate?.statusToolbar(self, menuActionDidPressed: .remove, menuButton: self.menuButton)
- }
- children.append(removeAction)
- }
-
- return UIMenu(title: "", options: [], children: children)
- }()
-
-
- menuButton.showsMenuAsPrimaryAction = true
- menuButton.accessibilityLabel = "Menu" // TODO: i18n
- }
-
-}
diff --git a/TwidereX.xcodeproj/project.pbxproj b/TwidereX.xcodeproj/project.pbxproj
index 2a06885e..1a7039c5 100644
--- a/TwidereX.xcodeproj/project.pbxproj
+++ b/TwidereX.xcodeproj/project.pbxproj
@@ -12,7 +12,6 @@
AF66F7A022E93DE9E329F6A3 /* Pods_TwidereXTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62DC6A6809A00EA057395C07 /* Pods_TwidereXTests.framework */; };
DB004BF626CE4A7F00F5C574 /* StatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB004BF526CE4A7F00F5C574 /* StatusCollectionViewCell.swift */; };
DB01091526E5EB64005F67D7 /* MastodonStatusThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01091426E5EB64005F67D7 /* MastodonStatusThreadViewModel.swift */; };
- DB01091A26E60693005F67D7 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AC0FF25401C2700E636BE /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; };
DB01092026E60756005F67D7 /* DataSourceFacade+StatusThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01091F26E60756005F67D7 /* DataSourceFacade+StatusThread.swift */; };
DB01092226E608B7005F67D7 /* DataSourceFacade+Repost.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01092126E608B7005F67D7 /* DataSourceFacade+Repost.swift */; };
DB01092426E6199C005F67D7 /* DataSourceProvider+StatusViewTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01092326E6199C005F67D7 /* DataSourceProvider+StatusViewTableViewCellDelegate.swift */; };
@@ -83,7 +82,6 @@
DB2EBBF0255D368200956CAA /* TableViewEntryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2EBBEF255D368200956CAA /* TableViewEntryRow.swift */; };
DB2FFF2E258B1077003DBC19 /* MosaicPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FFF2D258B1077003DBC19 /* MosaicPlayerView.swift */; };
DB2FFF3E258B78B0003DBC19 /* AVPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FFF3D258B78B0003DBC19 /* AVPlayer.swift */; };
- DB2FFF61258CB051003DBC19 /* NeedsDependency+AVPlayerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FFF60258CB051003DBC19 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */; };
DB30ADD626CF583900B2D2BE /* FLAnimatedImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB30ADD526CF583900B2D2BE /* FLAnimatedImage */; };
DB30ADDC26CFC7EE00B2D2BE /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB30ADDB26CFC7EE00B2D2BE /* StatusTableViewCell.swift */; };
DB30ADDD26CFD3CC00B2D2BE /* HomeTimelineViewController+DebugAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB2513D2599DBAB0064A876 /* HomeTimelineViewController+DebugAction.swift */; };
@@ -292,7 +290,6 @@
DBCC7AE6274BA10100E0986D /* OfficialTwitterTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01091B26E606BB005F67D7 /* OfficialTwitterTextProvider.swift */; };
DBCE2E912591A06300926D09 /* UINavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCE2E902591A06300926D09 /* UINavigationController.swift */; };
DBCE2E992591A44000926D09 /* UIViewAnimatingPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCE2E982591A44000926D09 /* UIViewAnimatingPosition.swift */; };
- DBCE2EE32593319000926D09 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCE2EE22593319000926D09 /* AvatarConfigurableView.swift */; };
DBD0B4972758B57F0015A388 /* DrawerSidebarTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFDC91255920060086F268 /* DrawerSidebarTransitionController.swift */; };
DBD0B4982758B58F0015A388 /* DrawerSidebarPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFCC46225668B860016698E /* DrawerSidebarPresentationController.swift */; };
DBD0B4992758B58F0015A388 /* DrawerSidebarAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB761E3E25592AA20050DC01 /* DrawerSidebarAnimatedTransitioning.swift */; };
@@ -323,6 +320,7 @@
DBEA4F842511F7460007FEC5 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = DBEA4F832511F7460007FEC5 /* Kanna */; };
DBEA4F8F251314640007FEC5 /* MosaicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEA4F8E251314640007FEC5 /* MosaicImageView.swift */; };
DBED96D8253F5D7800C5383A /* NamingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBED96D7253F5D7800C5383A /* NamingState.swift */; };
+ DBF167FD27C394830001F75E /* NeedsDependency+AvatarBarButtonItemDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF167FC27C394830001F75E /* NeedsDependency+AvatarBarButtonItemDelegate.swift */; };
DBF3309125B96E0B00A678FB /* WKNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF3309025B96E0B00A678FB /* WKNavigationDelegateShim.swift */; };
DBF3309925B988A500A678FB /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DBF3309825B988A500A678FB /* Settings.bundle */; };
DBF639EA259B11F6009E12C8 /* UserTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF639E9259B11F6009E12C8 /* UserTimelineViewModel+Diffable.swift */; };
@@ -527,7 +525,6 @@
DB2FFF2D258B1077003DBC19 /* MosaicPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MosaicPlayerView.swift; sourceTree = ""; };
DB2FFF35258B56E5003DBC19 /* VideoPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlaybackService.swift; sourceTree = ""; };
DB2FFF3D258B78B0003DBC19 /* AVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayer.swift; sourceTree = ""; };
- DB2FFF60258CB051003DBC19 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NeedsDependency+AVPlayerViewControllerDelegate.swift"; sourceTree = ""; };
DB30ADDB26CFC7EE00B2D2BE /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = ""; };
DB33017525A41102003552B0 /* StatusProviderFacade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProviderFacade.swift; sourceTree = ""; };
DB33A4A825A319A0003CED7D /* ActionToolbarContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionToolbarContainer.swift; sourceTree = ""; };
@@ -560,7 +557,6 @@
DB434882251DF18C005B599F /* ProfileBannerInfoActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileBannerInfoActionView.swift; sourceTree = ""; };
DB434887251DFE2D005B599F /* ProfileBannerStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileBannerStatusView.swift; sourceTree = ""; };
DB43488C251DFF0F005B599F /* ProfileBannerStatusItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileBannerStatusItemView.swift; sourceTree = ""; };
- DB43CBDC2595CE2100D47607 /* LoadMoreConfigurableViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableViewContainer.swift; sourceTree = ""; };
DB44A56126C4FEAB004C8B78 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = ""; };
DB4B599325DFB4D60012F495 /* APIService+Persist+PersistMemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistMemo.swift"; sourceTree = ""; };
DB4B59B425DFBDB80012F495 /* APIService+Persist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist.swift"; sourceTree = ""; };
@@ -682,7 +678,6 @@
DB89F58B257A2A57005ECD04 /* TimelineTopLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTopLoaderTableViewCell.swift; sourceTree = ""; };
DB89F590257A2A70005ECD04 /* TimelineLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLoaderTableViewCell.swift; sourceTree = ""; };
DB8AC0F725401BA200E636BE /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; };
- DB8AC0FF25401C2700E636BE /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = ""; };
DB8AC10D2540501600E636BE /* ComposeTweetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTweetViewController.swift; sourceTree = ""; };
DB8AC1462541677900E636BE /* ComposeTweetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTweetViewModel.swift; sourceTree = ""; };
DB8AC14B254167BE00E636BE /* TweetToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweetToolbarView.swift; sourceTree = ""; };
@@ -798,7 +793,6 @@
DBCE2EB12591F38100926D09 /* FriendshipListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendshipListViewModel.swift; sourceTree = ""; };
DBCE2EC72591F74300926D09 /* FollowingListViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewModel+Diffable.swift"; sourceTree = ""; };
DBCE2ED72591FDF000926D09 /* FollowingListViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewModel+State.swift"; sourceTree = ""; };
- DBCE2EE22593319000926D09 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = ""; };
DBCE2EE725933B1600926D09 /* SearchUserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchUserTableViewCell.swift; sourceTree = ""; };
DBD0B49C2758B5F50015A388 /* SidebarSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarSection.swift; sourceTree = ""; };
DBD0B49F2758B6010015A388 /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = ""; };
@@ -851,6 +845,7 @@
DBED96D7253F5D7800C5383A /* NamingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamingState.swift; sourceTree = ""; };
DBEFDC8725591F5C0086F268 /* DrawerSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerSidebarViewController.swift; sourceTree = ""; };
DBEFDC91255920060086F268 /* DrawerSidebarTransitionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerSidebarTransitionController.swift; sourceTree = ""; };
+ DBF167FC27C394830001F75E /* NeedsDependency+AvatarBarButtonItemDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NeedsDependency+AvatarBarButtonItemDelegate.swift"; sourceTree = ""; };
DBF3309025B96E0B00A678FB /* WKNavigationDelegateShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationDelegateShim.swift; sourceTree = ""; };
DBF3309825B988A500A678FB /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; };
DBF639E9259B11F6009E12C8 /* UserTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+Diffable.swift"; sourceTree = ""; };
@@ -975,16 +970,6 @@
path = Mastodon;
sourceTree = "";
};
- DB01091926E60668005F67D7 /* Deprecated */ = {
- isa = PBXGroup;
- children = (
- DB43CBDC2595CE2100D47607 /* LoadMoreConfigurableViewContainer.swift */,
- DB8AC0FF25401C2700E636BE /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */,
- DBCE2EE22593319000926D09 /* AvatarConfigurableView.swift */,
- );
- path = Deprecated;
- sourceTree = "";
- };
DB02C76E27350D75007EA0BF /* Hashtag */ = {
isa = PBXGroup;
children = (
@@ -2280,12 +2265,11 @@
DBED96DC253F5D7B00C5383A /* Protocol */ = {
isa = PBXGroup;
children = (
- DB01091926E60668005F67D7 /* Deprecated */,
DB5632A526DCC82D00FC893F /* Provider */,
+ DBF167FC27C394830001F75E /* NeedsDependency+AvatarBarButtonItemDelegate.swift */,
DBED96D7253F5D7800C5383A /* NamingState.swift */,
DBC8E04A2576337F00401E20 /* DisposeBagCollectable.swift */,
DB36F35D257F74C00028F81E /* ScrollViewContainer.swift */,
- DB2FFF60258CB051003DBC19 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */,
DB6B8B5E25AF30B900F20FD5 /* ContextMenuAction.swift */,
DB51DC3A2716F82000A0D8FB /* CellFrameCacheContainer.swift */,
);
@@ -2965,7 +2949,6 @@
DBDAF243274F530B00050319 /* SceneCoordinator.swift in Sources */,
DB25C4CC27799FE600EC1435 /* SavedSearchViewController.swift in Sources */,
DB830200273D04D000BF5224 /* NotificationTimelineViewModel+Diffable.swift in Sources */,
- DB01091A26E60693005F67D7 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
DBCB4060255CAC0300DD8D8F /* TwitterAuthenticationController.swift in Sources */,
DB5632B426DDE3F300FC893F /* StatusThreadViewModel+LoadThreadState.swift in Sources */,
DBD0B49D2758B5F50015A388 /* SidebarSection.swift in Sources */,
@@ -2979,7 +2962,6 @@
DBA21039275A0E77000B7CB2 /* FollowerListViewController.swift in Sources */,
DB676180254C042C006C6798 /* FriendshipTableViewCell.swift in Sources */,
DB5632C226DF8DE100FC893F /* TimelineViewModel+LoadOldestState.swift in Sources */,
- DBCE2EE32593319000926D09 /* AvatarConfigurableView.swift in Sources */,
DB3B906326E8BBD70010F64C /* ProfileHeaderView.swift in Sources */,
DB02C77227351B7D007EA0BF /* HashtagTableViewCell.swift in Sources */,
DB3B906726E8CD6D0010F64C /* ProfileHeaderViewModel.swift in Sources */,
@@ -3011,6 +2993,7 @@
DBC8E04B2576337F00401E20 /* DisposeBagCollectable.swift in Sources */,
DB71C7C6271D304900BE3819 /* UserLikeTimelineViewController+DataSourceProvider.swift in Sources */,
DBAA898E2758CF01001C273B /* DrawerSidebarHeaderView+ViewModel.swift in Sources */,
+ DBF167FD27C394830001F75E /* NeedsDependency+AvatarBarButtonItemDelegate.swift in Sources */,
DB02C77927352217007EA0BF /* SearchHashtagViewModel+Diffable.swift in Sources */,
DB262A4327229BE700D18EF3 /* SearchTimelineViewController.swift in Sources */,
DB25C4CA277999BE00EC1435 /* ButtonTableViewCell.swift in Sources */,
@@ -3138,7 +3121,6 @@
DB5FAFFD2727B940006520FA /* UserListFetchViewModel.swift in Sources */,
DB01092226E608B7005F67D7 /* DataSourceFacade+Repost.swift in Sources */,
DB67610D254AD681006C6798 /* ActivityIndicatorCollectionViewCell.swift in Sources */,
- DB2FFF61258CB051003DBC19 /* NeedsDependency+AVPlayerViewControllerDelegate.swift in Sources */,
DB94B6B426C65BE100A2E8A1 /* MastodonAuthenticationController.swift in Sources */,
DBA6B1B725355C1E005FB0B4 /* UIAlertController.swift in Sources */,
DB1BF29925663EFA00B5F490 /* DisplayPreference.swift in Sources */,
@@ -3317,11 +3299,11 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 7LFDZ96332;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 74;
+ DYLIB_CURRENT_VERSION = 77;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Twidere. All rights reserved.";
@@ -3351,11 +3333,11 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 7LFDZ96332;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 74;
+ DYLIB_CURRENT_VERSION = 77;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Twidere. All rights reserved.";
@@ -3381,7 +3363,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@@ -3407,7 +3389,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@@ -3557,7 +3539,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = TwidereX/TwidereX.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_ASSET_PATHS = "TwidereX/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = TwidereX/Info.plist;
@@ -3565,7 +3547,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.2.0;
+ MARKETING_VERSION = 1.2.5;
PRODUCT_BUNDLE_IDENTIFIER = com.twidere.TwidereX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -3583,7 +3565,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = TwidereX/TwidereX.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_ASSET_PATHS = "TwidereX/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = TwidereX/Info.plist;
@@ -3591,7 +3573,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.2.0;
+ MARKETING_VERSION = 1.2.5;
PRODUCT_BUNDLE_IDENTIFIER = com.twidere.TwidereX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -3606,7 +3588,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = TwidereXTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -3630,7 +3612,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = TwidereXTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -3653,7 +3635,7 @@
baseConfigurationReference = 2E41729E598B3379FC09BC03 /* Pods-TwidereX-TwidereXUITests.debug.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = TwidereXUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -3676,7 +3658,7 @@
baseConfigurationReference = 44D01BA9E21B7F374DBDA38A /* Pods-TwidereX-TwidereXUITests.release.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 74;
+ CURRENT_PROJECT_VERSION = 77;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = TwidereXUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
diff --git a/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved
index f0b19029..e333c21b 100644
--- a/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -10,15 +10,6 @@
"version": "5.5.0"
}
},
- {
- "package": "AlamofireImage",
- "repositoryURL": "https://github.com/Alamofire/AlamofireImage",
- "state": {
- "branch": null,
- "revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10",
- "version": "4.2.0"
- }
- },
{
"package": "CommonOSLog",
"repositoryURL": "https://github.com/MainasuK/CommonOSLog",
@@ -33,8 +24,8 @@
"repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git",
"state": {
"branch": null,
- "revision": "4b0565384d3c4c588af09e660535b2c7c9bf5b39",
- "version": "1.4.2"
+ "revision": "12f2389aca4a07e0dd54c86ec23d0721ed88b8db",
+ "version": "1.4.3"
}
},
{
@@ -51,7 +42,7 @@
"repositoryURL": "https://github.com/kciter/Floaty.git",
"state": {
"branch": "master",
- "revision": "1a6fd17ae1469c86baf830d9d5c93e6804cabe95",
+ "revision": "f8f6cf9970a3d92e98fcc2cf123b1bdef15e8778",
"version": null
}
},
@@ -105,8 +96,8 @@
"repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git",
"state": {
"branch": null,
- "revision": "86f6be38c2bc322fcdbec24a2377f8e67566f7da",
- "version": "3.2.1"
+ "revision": "95a092656315e38b433271a0470105be0025bd67",
+ "version": "3.3.1"
}
},
{
@@ -145,22 +136,13 @@
"version": "2.36.0"
}
},
- {
- "package": "SwiftMessages",
- "repositoryURL": "https://github.com/SwiftKickMobile/SwiftMessages",
- "state": {
- "branch": null,
- "revision": "b29dd21090b708aa0ae9ecbaf6e2d0487028dc3f",
- "version": "9.0.6"
- }
- },
{
"package": "Introspect",
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
"state": {
"branch": null,
- "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2",
- "version": "0.1.3"
+ "revision": "f2616860a41f9d9932da412a8978fec79c06fe24",
+ "version": "0.1.4"
}
},
{
@@ -177,8 +159,8 @@
"repositoryURL": "https://github.com/uias/Tabman.git",
"state": {
"branch": null,
- "revision": "f43489cdd743ba7ad86a422ebb5fcbf34e333df4",
- "version": "2.11.1"
+ "revision": "a9f10cb862a32e6a22549836af013abd6b0692d3",
+ "version": "2.12.0"
}
},
{
diff --git a/TwidereX/Coordinator/SceneCoordinator.swift b/TwidereX/Coordinator/SceneCoordinator.swift
index abcb523f..059506c3 100644
--- a/TwidereX/Coordinator/SceneCoordinator.swift
+++ b/TwidereX/Coordinator/SceneCoordinator.swift
@@ -74,16 +74,9 @@ extension SceneCoordinator {
case trend(viewModel: TrendViewModel)
case searchResult(viewModel: SearchResultViewModel)
- // TODO:
- // case tweetConversation(viewModel: TweetConversationViewModel)
-// case searchDetail(viewModel: SearchDetailViewModel)
-
-// case friendshipList(viewModel: FriendshipListViewModel)
-
case setting
case displayPreference
case about
- // end TODO:
#if DEBUG
case developer
diff --git a/TwidereX/Diffable/Msic/Notification/NotificationSection.swift b/TwidereX/Diffable/Msic/Notification/NotificationSection.swift
index 08f1f5a7..e9163b31 100644
--- a/TwidereX/Diffable/Msic/Notification/NotificationSection.swift
+++ b/TwidereX/Diffable/Msic/Notification/NotificationSection.swift
@@ -9,6 +9,7 @@
import UIKit
import AppShared
+import TwidereUI
enum NotificationSection: Hashable {
case main
@@ -17,8 +18,9 @@ enum NotificationSection: Hashable {
extension NotificationSection {
struct Configuration {
- let statusViewTableViewCellDelegate: StatusViewTableViewCellDelegate
- let userTableViewCellDelegate: UserTableViewCellDelegate?
+ weak var statusViewTableViewCellDelegate: StatusViewTableViewCellDelegate?
+ weak var userTableViewCellDelegate: UserTableViewCellDelegate?
+ let statusViewConfigurationContext: StatusView.ConfigurationContext
}
static func diffableDataSource(
@@ -29,9 +31,7 @@ extension NotificationSection {
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
// data source should dispatch in main thread
assert(Thread.isMainThread)
-
- let activeAuthenticationContext = context.authenticationService.activeAuthenticationContext.eraseToAnyPublisher()
-
+
// configure cell with item
switch item {
case .feed(let record):
@@ -46,20 +46,13 @@ extension NotificationSection {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
StatusSection.setupStatusPollDataSource(
context: context,
- managedObjectContext: context.managedObjectContext,
statusView: cell.statusView,
- configurationContext: PollOptionView.ConfigurationContext(
- dateTimeProvider: DateTimeSwiftProvider(),
- activeAuthenticationContext: activeAuthenticationContext
- )
+ configurationContext: configuration.statusViewConfigurationContext
)
configure(
tableView: tableView,
cell: cell,
- viewModel: StatusTableViewCell.ViewModel(
- value: .feed(feed),
- activeAuthenticationContext: context.authenticationService.activeAuthenticationContext.share().eraseToAnyPublisher()
- ),
+ viewModel: StatusTableViewCell.ViewModel(value: .feed(feed)),
configuration: configuration
)
return cell
@@ -67,7 +60,7 @@ extension NotificationSection {
switch object {
case .mastodon(let notification):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserNotificationStyleTableViewCell.self), for: indexPath) as! UserNotificationStyleTableViewCell
- let authenticationContext = context.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = context.authenticationService.activeAuthenticationContext
let me = authenticationContext?.user(in: context.managedObjectContext)
let user: UserObject = .mastodon(object: notification.account)
configure(
@@ -114,6 +107,7 @@ extension NotificationSection {
cell.configure(
tableView: tableView,
viewModel: viewModel,
+ configurationContext: configuration.statusViewConfigurationContext,
delegate: configuration.statusViewTableViewCellDelegate
)
}
diff --git a/TwidereX/Diffable/Status/StatusSection.swift b/TwidereX/Diffable/Status/StatusSection.swift
index ad5188f7..2d95d557 100644
--- a/TwidereX/Diffable/Status/StatusSection.swift
+++ b/TwidereX/Diffable/Status/StatusSection.swift
@@ -26,6 +26,7 @@ extension StatusSection {
struct Configuration {
weak var statusViewTableViewCellDelegate: StatusViewTableViewCellDelegate?
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
+ let statusViewConfigurationContext: StatusView.ConfigurationContext
}
static func diffableDataSource(
@@ -41,30 +42,21 @@ extension StatusSection {
// data source should dispatch in main thread
assert(Thread.isMainThread)
- let activeAuthenticationContext = context.authenticationService.activeAuthenticationContext.eraseToAnyPublisher()
-
// configure cell with item
switch item {
case .feed(let record):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
setupStatusPollDataSource(
context: context,
- managedObjectContext: context.managedObjectContext,
statusView: cell.statusView,
- configurationContext: PollOptionView.ConfigurationContext(
- dateTimeProvider: DateTimeSwiftProvider(),
- activeAuthenticationContext: activeAuthenticationContext
- )
+ configurationContext: configuration.statusViewConfigurationContext
)
context.managedObjectContext.performAndWait {
guard let feed = record.object(in: context.managedObjectContext) else { return }
configure(
tableView: tableView,
cell: cell,
- viewModel: StatusTableViewCell.ViewModel(
- value: .feed(feed),
- activeAuthenticationContext: activeAuthenticationContext
- ),
+ viewModel: StatusTableViewCell.ViewModel(value: .feed(feed)),
configuration: configuration
)
}
@@ -85,12 +77,8 @@ extension StatusSection {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
setupStatusPollDataSource(
context: context,
- managedObjectContext: context.managedObjectContext,
statusView: cell.statusView,
- configurationContext: PollOptionView.ConfigurationContext(
- dateTimeProvider: DateTimeSwiftProvider(),
- activeAuthenticationContext: activeAuthenticationContext
- )
+ configurationContext: configuration.statusViewConfigurationContext
)
context.managedObjectContext.performAndWait {
switch status {
@@ -99,10 +87,7 @@ extension StatusSection {
configure(
tableView: tableView,
cell: cell,
- viewModel: StatusTableViewCell.ViewModel(
- value: .twitterStatus(status),
- activeAuthenticationContext: activeAuthenticationContext
- ),
+ viewModel: StatusTableViewCell.ViewModel(value: .twitterStatus(status)),
configuration: configuration
)
case .mastodon(let record):
@@ -110,10 +95,7 @@ extension StatusSection {
configure(
tableView: tableView,
cell: cell,
- viewModel: StatusTableViewCell.ViewModel(
- value: .mastodonStatus(status),
- activeAuthenticationContext: activeAuthenticationContext
- ),
+ viewModel: StatusTableViewCell.ViewModel(value: .mastodonStatus(status)),
configuration: configuration
)
} // end switch
@@ -127,8 +109,6 @@ extension StatusSection {
indexPath: indexPath,
configuration: ThreadCellRegistrationConfiguration(
thread: thread,
- managedObjectContext: context.managedObjectContext,
- activeAuthenticationContext: activeAuthenticationContext,
configuration: configuration
)
)
@@ -151,8 +131,6 @@ extension StatusSection {
struct ThreadCellRegistrationConfiguration {
let thread: StatusItem.Thread
- let managedObjectContext: NSManagedObjectContext
- let activeAuthenticationContext: AnyPublisher
let configuration: Configuration
}
@@ -162,26 +140,24 @@ extension StatusSection {
indexPath: IndexPath,
configuration: ThreadCellRegistrationConfiguration
) -> UITableViewCell {
+ let managedObjectContext = context.managedObjectContext
+
+ let configurationContext = configuration.configuration.statusViewConfigurationContext
+
switch configuration.thread {
case .root(let threadContext):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusThreadRootTableViewCell.self), for: indexPath) as! StatusThreadRootTableViewCell
setupStatusPollDataSource(
context: context,
- managedObjectContext: configuration.managedObjectContext,
statusView: cell.statusView,
- configurationContext: PollOptionView.ConfigurationContext(
- dateTimeProvider: DateTimeSwiftProvider(),
- activeAuthenticationContext: configuration.activeAuthenticationContext
- )
+ configurationContext: configurationContext
)
- configuration.managedObjectContext.performAndWait {
- guard let status = threadContext.status.object(in: configuration.managedObjectContext) else { return }
+ managedObjectContext.performAndWait {
+ guard let status = threadContext.status.object(in: managedObjectContext) else { return }
cell.configure(
tableView: tableView,
- viewModel: StatusThreadRootTableViewCell.ViewModel(
- value: .statusObject(status),
- activeAuthenticationContext: configuration.activeAuthenticationContext
- ),
+ viewModel: StatusThreadRootTableViewCell.ViewModel(value: .statusObject(status)),
+ configurationContext: configurationContext,
delegate: configuration.configuration.statusViewTableViewCellDelegate
)
}
@@ -194,21 +170,15 @@ extension StatusSection {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
setupStatusPollDataSource(
context: context,
- managedObjectContext: configuration.managedObjectContext,
statusView: cell.statusView,
- configurationContext: PollOptionView.ConfigurationContext(
- dateTimeProvider: DateTimeSwiftProvider(),
- activeAuthenticationContext: configuration.activeAuthenticationContext
- )
+ configurationContext: configurationContext
)
- configuration.managedObjectContext.performAndWait {
- guard let status = threadContext.status.object(in: configuration.managedObjectContext) else { return }
+ managedObjectContext.performAndWait {
+ guard let status = threadContext.status.object(in: managedObjectContext) else { return }
cell.configure(
tableView: tableView,
- viewModel: StatusTableViewCell.ViewModel(
- value: .statusObject(status),
- activeAuthenticationContext: configuration.activeAuthenticationContext
- ),
+ viewModel: StatusTableViewCell.ViewModel(value: .statusObject(status)),
+ configurationContext: configurationContext,
delegate: configuration.configuration.statusViewTableViewCellDelegate
)
}
@@ -224,10 +194,10 @@ extension StatusSection {
public static func setupStatusPollDataSource(
context: AppContext,
- managedObjectContext: NSManagedObjectContext,
statusView: StatusView,
configurationContext: PollOptionView.ConfigurationContext
) {
+ let managedObjectContext = context.managedObjectContext
statusView.pollTableViewDiffableDataSource = UITableViewDiffableDataSource(tableView: statusView.pollTableView) { tableView, indexPath, item in
switch item {
case .option(let record):
@@ -276,7 +246,7 @@ extension StatusSection {
}()
if needsUpdatePoll,
- case let .mastodon(authenticationContext) = context.authenticationService.activeAuthenticationContext.value
+ case let .mastodon(authenticationContext) = context.authenticationService.activeAuthenticationContext
{
let status: ManagedObjectRecord = .init(objectID: option.poll.status.objectID)
Task { [weak context] in
@@ -309,6 +279,7 @@ extension StatusSection {
cell.configure(
tableView: tableView,
viewModel: viewModel,
+ configurationContext: configuration.statusViewConfigurationContext,
delegate: configuration.statusViewTableViewCellDelegate
)
}
diff --git a/TwidereX/Diffable/User/UserSection.swift b/TwidereX/Diffable/User/UserSection.swift
index 8e8b7d50..e8e51b51 100644
--- a/TwidereX/Diffable/User/UserSection.swift
+++ b/TwidereX/Diffable/User/UserSection.swift
@@ -66,7 +66,7 @@ extension UserSection {
let cell = dequeueReusableCell(tableView: tableView, indexPath: indexPath, style: style)
context.managedObjectContext.performAndWait {
guard let user = record.object(in: context.managedObjectContext) else { return }
- let authenticationContext = context.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = context.authenticationService.activeAuthenticationContext
let me = authenticationContext?.user(in: context.managedObjectContext)
let viewModel = UserTableViewCell.ViewModel(
user: user,
diff --git a/TwidereX/Info.plist b/TwidereX/Info.plist
index 0f043fce..ace1f2c9 100644
--- a/TwidereX/Info.plist
+++ b/TwidereX/Info.plist
@@ -19,9 +19,9 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.4
+ 1.2.6
CFBundleVersion
- 74
+ 77
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/TwidereX/Protocol/Deprecated/AvatarConfigurableView.swift b/TwidereX/Protocol/Deprecated/AvatarConfigurableView.swift
deleted file mode 100644
index d147494a..00000000
--- a/TwidereX/Protocol/Deprecated/AvatarConfigurableView.swift
+++ /dev/null
@@ -1,217 +0,0 @@
-//
-// AvatarConfigurableView.swift
-// TwidereX
-//
-// Created by Cirno MainasuK on 2020-12-23.
-// Copyright © 2020 Twidere. All rights reserved.
-//
-
-import UIKit
-import AlamofireImage
-import Kingfisher
-
-//@available(*, deprecated, message: "")
-//protocol AvatarConfigurableView {
-// static var configurableAvatarImageViewSize: CGSize { get }
-// static var configurableAvatarImageViewBadgeAppearanceStyle: AvatarConfigurableViewConfiguration.BadgeAppearanceStyle { get }
-// var configurableAvatarImageView: UIImageView? { get }
-// var configurableAvatarButton: UIButton? { get }
-// var configurableVerifiedBadgeImageView: UIImageView? { get }
-// func configure(withConfigurationInput input: AvatarConfigurableViewConfiguration.Input)
-// func avatarConfigurableView(_ avatarConfigurableView: AvatarConfigurableView, didFinishConfiguration configuration: AvatarConfigurableViewConfiguration)
-//}
-//
-//@available(*, deprecated, message: "")
-//extension AvatarConfigurableView {
-//
-// static var configurableAvatarImageViewBadgeAppearanceStyle: AvatarConfigurableViewConfiguration.BadgeAppearanceStyle { return .mini }
-//
-// public func configure(withConfigurationInput input: AvatarConfigurableViewConfiguration.Input) {
-// // set badge
-// switch (input.verified, input.blocked) {
-// case (_, true):
-// configurableVerifiedBadgeImageView?.isHidden = false
-// switch Self.configurableAvatarImageViewBadgeAppearanceStyle {
-// case .mini:
-// assertionFailure()
-// configurableVerifiedBadgeImageView?.image = Asset.ObjectTools.blockedBadge.image.withRenderingMode(.alwaysOriginal)
-// case .normal:
-// configurableVerifiedBadgeImageView?.image = Asset.ObjectTools.blockedBadge.image.withRenderingMode(.alwaysOriginal)
-// }
-// case (true, false):
-// configurableVerifiedBadgeImageView?.isHidden = false
-// switch Self.configurableAvatarImageViewBadgeAppearanceStyle {
-// case .mini:
-// break
-//// configurableVerifiedBadgeImageView?.image = Asset.ObjectTools.verifiedBadgeMini.image.withRenderingMode(.alwaysOriginal)
-// case .normal:
-// break
-//// configurableVerifiedBadgeImageView?.image = Asset.ObjectTools.verifiedBadge.image.withRenderingMode(.alwaysOriginal)
-// }
-// default:
-// configurableVerifiedBadgeImageView?.isHidden = true
-// }
-//
-// let avatarStyle = UserDefaults.shared.avatarStyle
-// let roundedSquareCornerRadius = AvatarConfigurableViewConfiguration.roundedSquareCornerRadius(for: Self.configurableAvatarImageViewSize)
-// let cornerRadius = AvatarConfigurableViewConfiguration.cornerRadius(for: Self.configurableAvatarImageViewSize, avatarStyle: avatarStyle)
-// let scale = (configurableAvatarImageView ?? configurableAvatarButton)?.window?.screen.scale ?? UIScreen.main.scale
-//
-// let placeholderImage: UIImage = {
-// let placeholderImage = input.placeholderImage ?? UIImage.placeholder(size: Self.configurableAvatarImageViewSize, color: .systemFill)
-// switch avatarStyle {
-// case .circle: return placeholderImage.af.imageRoundedIntoCircle()
-// case .roundedSquare: return placeholderImage.af.imageRounded(withCornerRadius: roundedSquareCornerRadius * placeholderImage.scale, divideRadiusByImageScale: true)
-// }
-//
-// }()
-//
-// // cancel previous task
-// configurableAvatarImageView?.af.cancelImageRequest()
-// configurableAvatarImageView?.kf.cancelDownloadTask()
-// configurableAvatarButton?.af.cancelImageRequest(for: .normal)
-// configurableAvatarButton?.kf.cancelImageDownloadTask()
-//
-// // reset layer attributes
-// configurableAvatarImageView?.layer.masksToBounds = false
-// configurableAvatarImageView?.layer.cornerRadius = 0
-// configurableAvatarImageView?.layer.cornerCurve = .circular
-//
-// configurableAvatarButton?.layer.masksToBounds = false
-// configurableAvatarButton?.layer.cornerRadius = 0
-// configurableAvatarButton?.layer.cornerCurve = .circular
-//
-// defer {
-// let configuration = AvatarConfigurableViewConfiguration(
-// input: input,
-// output: AvatarConfigurableViewConfiguration.Output(cornerRadius: cornerRadius)
-// )
-// avatarConfigurableView(self, didFinishConfiguration: configuration)
-// }
-//
-// // set placeholder if no asset
-// guard let avatarImageURL = input.avatarImageURL else {
-// configurableAvatarImageView?.image = placeholderImage
-// configurableAvatarButton?.setImage(placeholderImage, for: .normal)
-// return
-// }
-//
-// if let avatarImageView = configurableAvatarImageView {
-// // set avatar (GIF using Kingfisher)
-// switch avatarImageURL.pathExtension {
-// case "gif":
-// avatarImageView.kf.setImage(
-// with: avatarImageURL,
-// placeholder: placeholderImage,
-// options: [
-// .transition(.fade(0.2))
-// ]
-// )
-// avatarImageView.layer.masksToBounds = true
-// avatarImageView.layer.cornerRadius = cornerRadius
-// switch avatarStyle {
-// case .circle: avatarImageView.layer.cornerCurve = .circular
-// case .roundedSquare: avatarImageView.layer.cornerCurve = .continuous
-// }
-// default:
-// let filter = avatarImageFilter(for: avatarStyle, roundedSquareCornerRadius: roundedSquareCornerRadius, scale: scale)
-// avatarImageView.af.setImage(
-// withURL: avatarImageURL,
-// placeholderImage: placeholderImage,
-// filter: filter,
-// imageTransition: .crossDissolve(0.3),
-// runImageTransitionIfCached: false,
-// completion: nil
-// )
-// }
-// }
-//
-// if let avatarButton = configurableAvatarButton {
-// switch avatarImageURL.pathExtension {
-// case "gif":
-// avatarButton.kf.setImage(
-// with: avatarImageURL,
-// for: .normal,
-// placeholder: placeholderImage,
-// options: [
-// .transition(.fade(0.2))
-// ]
-// )
-// avatarButton.layer.masksToBounds = true
-// avatarButton.layer.cornerRadius = cornerRadius
-// switch avatarStyle {
-// case .circle: avatarButton.layer.cornerCurve = .circular
-// case .roundedSquare: avatarButton.layer.cornerCurve = .continuous
-// }
-// default:
-// let filter = avatarImageFilter(for: avatarStyle, roundedSquareCornerRadius: roundedSquareCornerRadius, scale: scale)
-// avatarButton.af.setImage(
-// for: .normal,
-// url: avatarImageURL,
-// placeholderImage: placeholderImage,
-// filter: filter,
-// completion: nil
-// )
-// }
-// }
-// }
-//
-// func avatarConfigurableView(_ avatarConfigurableView: AvatarConfigurableView, didFinishConfiguration configuration: AvatarConfigurableViewConfiguration) { }
-//
-//}
-//
-//extension AvatarConfigurableView {
-//
-// private func avatarImageFilter(for avatarStyle: UserDefaults.AvatarStyle, roundedSquareCornerRadius radius: CGFloat, scale: CGFloat) -> ImageFilter {
-// switch avatarStyle {
-// case .circle: return ScaledToSizeCircleFilter(size: Self.configurableAvatarImageViewSize)
-// case .roundedSquare: return AspectScaledToFillSizeWithRoundedCornersFilter(size: Self.configurableAvatarImageViewSize, radius: radius * scale, divideRadiusByImageScale: true)
-// }
-// }
-//
-//}
-//
-//struct AvatarConfigurableViewConfiguration {
-//
-// enum BadgeAppearanceStyle {
-// case mini
-// case normal
-// }
-//
-// struct Input {
-// let avatarImageURL: URL?
-// let placeholderImage: UIImage?
-// let blocked: Bool
-// let verified: Bool
-//
-// init(avatarImageURL: URL?, placeholderImage: UIImage? = nil, blocked: Bool = false, verified: Bool = false) {
-// self.avatarImageURL = avatarImageURL
-// self.placeholderImage = placeholderImage
-// self.blocked = blocked
-// self.verified = verified
-// }
-// }
-//
-// struct Output {
-// let cornerRadius: CGFloat
-// }
-//
-// let input: Input
-// let output: Output
-//
-// static func roundedSquareCornerRadius(for imageSize: CGSize) -> CGFloat {
-// return CGFloat(Int(imageSize.width) / 8 * 2) // even number from quoter of width
-// }
-//
-// static func cornerRadius(for imageSize: CGSize, avatarStyle: UserDefaults.AvatarStyle) -> CGFloat {
-// let roundedSquareCornerRadius = Self.roundedSquareCornerRadius(for: imageSize)
-// let cornerRadius: CGFloat = {
-// switch avatarStyle {
-// case .circle: return 0.5 * imageSize.width
-// case .roundedSquare: return roundedSquareCornerRadius
-// }
-// }()
-// return cornerRadius
-// }
-//
-//}
diff --git a/TwidereX/Protocol/Deprecated/ContentOffsetAdjustableTimelineViewControllerDelegate.swift b/TwidereX/Protocol/Deprecated/ContentOffsetAdjustableTimelineViewControllerDelegate.swift
deleted file mode 100644
index 142f7df0..00000000
--- a/TwidereX/Protocol/Deprecated/ContentOffsetAdjustableTimelineViewControllerDelegate.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// ContentOffsetAdjustableTimelineViewControllerDelegate.swift
-// TwidereX
-//
-// Created by Cirno MainasuK on 2020-10-21.
-// Copyright © 2020 Twidere. All rights reserved.
-//
-
-import UIKit
-
-@available(*, deprecated, message: "")
-protocol ContentOffsetAdjustableTimelineViewControllerDelegate: class {
- func navigationBar() -> UINavigationBar?
-}
-
diff --git a/TwidereX/Protocol/Deprecated/LoadMoreConfigurableViewContainer.swift b/TwidereX/Protocol/Deprecated/LoadMoreConfigurableViewContainer.swift
deleted file mode 100644
index 868fd85c..00000000
--- a/TwidereX/Protocol/Deprecated/LoadMoreConfigurableViewContainer.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// LoadMoreConfigurableViewContainer.swift
-// TwidereX
-//
-// Created by Cirno MainasuK on 2020-12-25.
-// Copyright © 2020 Twidere. All rights reserved.
-//
-
-import UIKit
-import GameplayKit
-
-/// The tableView container driven by state machines with "LoadMore" logic
-//protocol LoadMoreConfigurableTableViewContainer: UIViewController {
-//
-// associatedtype BottomLoaderTableViewCell: UITableViewCell
-// associatedtype LoadingState: GKState
-//
-// var loadMoreConfigurableTableView: UITableView { get }
-// var loadMoreConfigurableStateMachine: GKStateMachine { get }
-// func handleScrollViewDidScroll(_ scrollView: UIScrollView)
-//}
-//
-//extension LoadMoreConfigurableTableViewContainer {
-// func handleScrollViewDidScroll(_ scrollView: UIScrollView) {
-// guard scrollView === loadMoreConfigurableTableView else { return }
-//
-// // check if current scroll position is the bottom of table
-// let contentOffsetY = loadMoreConfigurableTableView.contentOffset.y
-// let bottomVisiblePageContentOffsetY = loadMoreConfigurableTableView.contentSize.height - (1.5 * loadMoreConfigurableTableView.visibleSize.height)
-// guard contentOffsetY > bottomVisiblePageContentOffsetY else {
-// return
-// }
-//
-// let cells = loadMoreConfigurableTableView.visibleCells.compactMap { $0 as? BottomLoaderTableViewCell }
-// guard let loaderTableViewCell = cells.first else { return }
-//
-// if let tabBar = tabBarController?.tabBar, let window = view.window {
-// let loaderTableViewCellFrameInWindow = loadMoreConfigurableTableView.convert(loaderTableViewCell.frame, to: nil)
-// let windowHeight = window.frame.height
-// let loaderAppear = (loaderTableViewCellFrameInWindow.origin.y + 0.8 * loaderTableViewCell.frame.height) < (windowHeight - tabBar.frame.height)
-// if loaderAppear {
-// loadMoreConfigurableStateMachine.enter(LoadingState.self)
-// } else {
-// // do nothing
-// }
-// } else {
-// loadMoreConfigurableStateMachine.enter(LoadingState.self)
-// }
-// }
-//}
diff --git a/TwidereX/Protocol/NeedsDependency+AVPlayerViewControllerDelegate.swift b/TwidereX/Protocol/NeedsDependency+AVPlayerViewControllerDelegate.swift
deleted file mode 100644
index 7cc789dc..00000000
--- a/TwidereX/Protocol/NeedsDependency+AVPlayerViewControllerDelegate.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-//
-// NeedsDependency+AVPlayerViewControllerDelegate.swift
-// TwidereX
-//
-// Created by Cirno MainasuK on 2020-12-18.
-// Copyright © 2020 Twidere. All rights reserved.
-//
-
-import Foundation
-import AVKit
-
-extension NeedsDependency where Self: AVPlayerViewControllerDelegate {
-
- func handlePlayerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
-// context.videoPlaybackService.playerViewModel(for: playerViewController)?.isFullScreenPresentationing = true
- }
-
- func handlePlayerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
-// context.videoPlaybackService.playerViewModel(for: playerViewController)?.isFullScreenPresentationing = false
- }
-
-}
diff --git a/TwidereX/Protocol/NeedsDependency+AvatarBarButtonItemDelegate.swift b/TwidereX/Protocol/NeedsDependency+AvatarBarButtonItemDelegate.swift
new file mode 100644
index 00000000..0a3b4f14
--- /dev/null
+++ b/TwidereX/Protocol/NeedsDependency+AvatarBarButtonItemDelegate.swift
@@ -0,0 +1,31 @@
+//
+// NeedsDependency+AvatarBarButtonItemDelegate.swift
+// TwidereX
+//
+// Created by MainasuK on 2022-2-21.
+// Copyright © 2022 Twidere. All rights reserved.
+//
+
+import UIKit
+
+// MARK: - AvatarBarButtonItemDelegate
+extension NeedsDependency where Self: AvatarBarButtonItemDelegate {
+
+ func avatarBarButtonItem(
+ _ barButtonItem: AvatarBarButtonItem,
+ didLongPressed sender: UILongPressGestureRecognizer
+ ) {
+ Task { @MainActor in
+ let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
+ feedbackGenerator.impactOccurred()
+
+ let accountListViewModel = AccountListViewModel(context: context)
+ coordinator.present(
+ scene: .accountList(viewModel: accountListViewModel),
+ from: nil,
+ transition: .modal(animated: true, completion: nil)
+ )
+ } // end Task
+ }
+
+}
diff --git a/TwidereX/Protocol/Provider/DataSourceFacade+Poll.swift b/TwidereX/Protocol/Provider/DataSourceFacade+Poll.swift
index e70226c7..2b2dfde2 100644
--- a/TwidereX/Protocol/Provider/DataSourceFacade+Poll.swift
+++ b/TwidereX/Protocol/Provider/DataSourceFacade+Poll.swift
@@ -127,7 +127,7 @@ extension DataSourceFacade {
status: ManagedObjectRecord,
voteButtonDidPressed button: UIButton
) async throws {
- guard case let .mastodon(authenticationContext) = provider.context.authenticationService.activeAuthenticationContext.value else { return }
+ guard case let .mastodon(authenticationContext) = provider.context.authenticationService.activeAuthenticationContext else { return }
// should use same context on UI to make transient property trigger update
let managedObjectContext = provider.context.managedObjectContext
diff --git a/TwidereX/Protocol/Provider/DataSourceFacade+Status.swift b/TwidereX/Protocol/Provider/DataSourceFacade+Status.swift
index 921e3e4e..1bef6f2c 100644
--- a/TwidereX/Protocol/Provider/DataSourceFacade+Status.swift
+++ b/TwidereX/Protocol/Provider/DataSourceFacade+Status.swift
@@ -36,8 +36,11 @@ extension DataSourceFacade {
apiService: provider.context.apiService,
authenticationService: provider.context.authenticationService,
mastodonEmojiService: provider.context.mastodonEmojiService,
- dateTimeProvider: DateTimeSwiftProvider(),
- twitterTextProvider: OfficialTwitterTextProvider()
+ statusViewConfigureContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: provider.context.authenticationService.$activeAuthenticationContext
+ )
)
)
provider.coordinator.present(
@@ -103,13 +106,14 @@ extension DataSourceFacade {
provider: DataSourceProvider,
status: StatusRecord
) async throws {
- try await provider.context.backgroundManagedObjectContext.performChanges {
- guard let object = status.object(in: provider.context.managedObjectContext) else { return }
+ let managedObjectContext = provider.context.managedObjectContext
+ try await managedObjectContext.performChanges {
+ guard let object = status.object(in: managedObjectContext) else { return }
switch object {
case .twitter:
break
case .mastodon(let status):
- status.update(isContentReveal: !status.isContentReveal)
+ status.update(isContentSensitiveToggled: !status.isContentSensitiveToggled)
}
}
}
diff --git a/TwidereX/Protocol/Provider/DataSourceProvider+MediaInfoDescriptionViewDelegate.swift b/TwidereX/Protocol/Provider/DataSourceProvider+MediaInfoDescriptionViewDelegate.swift
index f3198662..32c2d86b 100644
--- a/TwidereX/Protocol/Provider/DataSourceProvider+MediaInfoDescriptionViewDelegate.swift
+++ b/TwidereX/Protocol/Provider/DataSourceProvider+MediaInfoDescriptionViewDelegate.swift
@@ -74,7 +74,7 @@ extension MediaInfoDescriptionViewDelegate where Self: DataSourceProvider {
func mediaInfoDescriptionView(_ mediaInfoDescriptionView: MediaInfoDescriptionView, statusToolbar: StatusToolbar, actionDidPressed action: StatusToolbar.Action, button: UIButton) {
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
Task {
let source = DataSourceItem.Source(tableViewCell: nil, indexPath: nil)
guard let item = await item(from: source) else {
@@ -97,7 +97,7 @@ extension MediaInfoDescriptionViewDelegate where Self: DataSourceProvider {
} // end func
func mediaInfoDescriptionView(_ mediaInfoDescriptionView: MediaInfoDescriptionView, statusToolbar: StatusToolbar, menuActionDidPressed action: StatusToolbar.MenuAction, menuButton button: UIButton) {
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
}
diff --git a/TwidereX/Protocol/Provider/DataSourceProvider+StatusViewTableViewCellDelegate.swift b/TwidereX/Protocol/Provider/DataSourceProvider+StatusViewTableViewCellDelegate.swift
index f01e39f5..8d56fd43 100644
--- a/TwidereX/Protocol/Provider/DataSourceProvider+StatusViewTableViewCellDelegate.swift
+++ b/TwidereX/Protocol/Provider/DataSourceProvider+StatusViewTableViewCellDelegate.swift
@@ -321,7 +321,7 @@ extension StatusViewTableViewCellDelegate where Self: DataSourceProvider {
actionDidPressed action: StatusToolbar.Action,
button: UIButton
) {
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
Task {
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
guard let item = await item(from: source) else {
@@ -350,7 +350,7 @@ extension StatusViewTableViewCellDelegate where Self: DataSourceProvider {
menuActionDidPressed action: StatusToolbar.MenuAction,
menuButton button: UIButton
) {
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
Task {
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
guard let item = await item(from: source) else {
diff --git a/TwidereX/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/TwidereX/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
index c40232eb..681519d3 100644
--- a/TwidereX/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
+++ b/TwidereX/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift
@@ -120,17 +120,27 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewTrans
return nil
}
- func aspectTableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
+ func aspectTableView(
+ _ tableView: UITableView,
+ previewForHighlightingContextMenuWithConfiguration
+ configuration: UIContextMenuConfiguration
+ ) -> UITargetedPreview? {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
return aspectTableView(tableView, configuration: configuration)
}
- func aspectTableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
+ func aspectTableView(
+ _ tableView: UITableView,
+ previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
+ ) -> UITargetedPreview? {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
return aspectTableView(tableView, configuration: configuration)
}
- private func aspectTableView(_ tableView: UITableView, configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
+ private func aspectTableView(
+ _ tableView: UITableView,
+ configuration: UIContextMenuConfiguration
+ ) -> UITargetedPreview? {
guard let configuration = configuration as? TimelineTableViewCellContextMenuConfiguration else { return nil }
guard let indexPath = configuration.indexPath, let index = configuration.index else { return nil }
if let cell = tableView.cellForRow(at: indexPath) as? StatusViewContainerTableViewCell {
@@ -146,7 +156,11 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewTrans
}
}
- func aspectTableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
+ func aspectTableView(
+ _ tableView: UITableView,
+ willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration,
+ animator: UIContextMenuInteractionCommitAnimating
+ ) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
guard let configuration = configuration as? TimelineTableViewCellContextMenuConfiguration else { return }
guard let indexPath = configuration.indexPath, let index = configuration.index else { return }
@@ -179,6 +193,6 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewTrans
)
}
}
- }
+ } // end func
}
diff --git a/TwidereX/Scene/Account/List/AccountListViewController.swift b/TwidereX/Scene/Account/List/AccountListViewController.swift
index 5e45b87d..ced42280 100644
--- a/TwidereX/Scene/Account/List/AccountListViewController.swift
+++ b/TwidereX/Scene/Account/List/AccountListViewController.swift
@@ -14,10 +14,6 @@ import CoreDataStack
import TwitterSDK
import TwidereCommon
-protocol AccountListViewControllerDelegate: AnyObject {
- func signoutTwitterUser(id: TwitterUser.ID)
-}
-
final class AccountListViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
@@ -43,7 +39,7 @@ final class AccountListViewController: UIViewController, NeedsDependency {
}()
deinit {
- os_log("%{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)
}
}
@@ -131,65 +127,5 @@ extension AccountListViewController: UITableViewDelegate {
}
}
-// MARK: - AccountListTableViewCellDelegate
-extension AccountListViewController: AccountListTableViewCellDelegate {
-
- func accountListTableViewCell(_ cell: AccountListTableViewCell, menuButtonPressed button: UIButton) {
-// guard let diffableDataSource = viewModel.diffableDataSource else { return }
-// guard let indexPath = tableView.indexPath(for: cell) else { return }
-// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
-// guard case let .twitterUser(objectID) = item else { return }
-//
-// let managedObjectContext = context.managedObjectContext
-// managedObjectContext.perform {
-// guard let twitterUser = managedObjectContext.object(with: objectID) as? TwitterUser else { return }
-// let title = twitterUser.name
-//
-// DispatchQueue.main.async { [weak self] in
-// let alertController = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
-// let signOutAction = UIAlertAction(title: L10n.Scene.ManageAccounts.deleteAccount.localizedCapitalized, style: .destructive) { [weak self] _ in
-// guard let self = self else { return }
-// self.signoutTwitterUser(id: twitterUser.id)
-// }
-// let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
-// alertController.addAction(signOutAction)
-// alertController.addAction(cancelAction)
-// guard let self = self else { return }
-// alertController.popoverPresentationController?.sourceView = button
-// self.present(alertController, animated: true, completion: nil)
-// }
-// }
- }
-
-}
-
-// MARK: - AccountListViewControllerDelegate
-extension AccountListViewController: AccountListViewControllerDelegate {
-
- func signoutTwitterUser(id: TwitterUser.ID) {
-// let currentAccountCount = viewModel.diffableDataSource.snapshot().itemIdentifiers.count
-// context.authenticationService.signOutTwitterUser(id: id)
-// .receive(on: DispatchQueue.main)
-// .sink { [weak self] result in
-// guard let self = self else { return }
-// switch result {
-// case .failure(let error):
-// assertionFailure(error.localizedDescription)
-// case .success(let isSignOut):
-// guard isSignOut else { return }
-// self.dismiss(animated: true) {
-// if currentAccountCount == 1 {
-// // No active user. Present Authentication scene
-// let authenticationViewModel = AuthenticationViewModel(isAuthenticationIndexExist: false)
-// self.coordinator.present(scene: .authentication(viewModel: authenticationViewModel), from: nil, transition: .modal(animated: true, completion: nil))
-// }
-// }
-// }
-// }
-// .store(in: &disposeBag)
- }
-
-}
-
// MARK: - UserTableViewCellDelegate
extension AccountListViewController: UserTableViewCellDelegate { }
diff --git a/TwidereX/Scene/Account/List/AccountListViewModel+Diffable.swift b/TwidereX/Scene/Account/List/AccountListViewModel+Diffable.swift
index 47c3690d..03a98992 100644
--- a/TwidereX/Scene/Account/List/AccountListViewModel+Diffable.swift
+++ b/TwidereX/Scene/Account/List/AccountListViewModel+Diffable.swift
@@ -47,57 +47,4 @@ extension AccountListViewModel {
.store(in: &disposeBag)
}
-// static func configure(cell: AccountListTableViewCell, twitterUser: TwitterUser, accountListViewControllerDelegate: AccountListViewControllerDelegate?) {
-// // set avatar
-// let avatarImageURL = twitterUser.avatarImageURL()
-// let verified = twitterUser.verified
-// UserDefaults.shared
-// .observe(\.avatarStyle, options: [.initial, .new]) { defaults, _ in
-// cell.userBriefInfoView.configure(withConfigurationInput: AvatarConfigurableViewConfiguration.Input(avatarImageURL: avatarImageURL, verified: verified))
-// }
-// .store(in: &cell.observations)
-//
-// cell.userBriefInfoView.lockImageView.isHidden = !twitterUser.protected
-//
-// // set name and username
-// cell.userBriefInfoView.nameLabel.text = twitterUser.name
-// cell.userBriefInfoView.headerSecondaryLabel.text = ""
-//
-// cell.userBriefInfoView.detailLabel.text = "@" + twitterUser.username
-//
-// if let accountListViewControllerDelegate = accountListViewControllerDelegate {
-// if #available(iOS 14.0, *) {
-// let menuItems = [
-// UIMenu(
-// title: L10n.Scene.ManageAccounts.deleteAccount,
-// options: .destructive,
-// children: [
-// UIAction(
-// title: L10n.Common.Controls.Actions.remove,
-// image: nil,
-// attributes: .destructive,
-// state: .off,
-// handler: { [weak accountListViewControllerDelegate] _ in
-// accountListViewControllerDelegate?.signoutTwitterUser(id: twitterUser.id)
-// }
-// ),
-// UIAction(
-// title: L10n.Common.Controls.Actions.cancel,
-// attributes: [],
-// state: .off,
-// handler: { _ in
-// // do nothing
-// }
-// )
-// ]
-// )
-// ]
-// cell.userBriefInfoView.menuButton.menu = UIMenu(title: "", children: menuItems)
-// cell.userBriefInfoView.menuButton.showsMenuAsPrimaryAction = true
-// } else {
-// // delegate handle the button
-// }
-// }
-// }
-
}
diff --git a/TwidereX/Scene/Account/List/View/AccountListTableViewCell.swift b/TwidereX/Scene/Account/List/View/AccountListTableViewCell.swift
index 3abd7ed8..fbf39be2 100644
--- a/TwidereX/Scene/Account/List/View/AccountListTableViewCell.swift
+++ b/TwidereX/Scene/Account/List/View/AccountListTableViewCell.swift
@@ -9,21 +9,16 @@
import os.log
import UIKit
import Combine
-
-protocol AccountListTableViewCellDelegate: AnyObject {
- func accountListTableViewCell(_ cell: AccountListTableViewCell, menuButtonPressed button: UIButton)
-}
+import TwidereUI
final class AccountListTableViewCell: UITableViewCell {
var disposeBag = Set()
var observations = Set()
- weak var delegate: AccountListTableViewCellDelegate?
-
let userBriefInfoView = UserBriefInfoView()
- let separatorLine = UIView.separatorLine
+ let separatorLine = SeparatorLineView()
override func prepareForReuse() {
super.prepareForReuse()
@@ -72,11 +67,3 @@ extension AccountListTableViewCell {
}
}
-
-extension AccountListTableViewCell {
- @objc private func menuButtonPressed(_ sender: UIButton) {
- os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
-
- delegate?.accountListTableViewCell(self, menuButtonPressed: sender)
- }
-}
diff --git a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewController.swift
index ed2c4505..c17c46c0 100644
--- a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewController.swift
+++ b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewController.swift
@@ -130,8 +130,11 @@ extension HashtagTimelineViewController {
apiService: context.apiService,
authenticationService: context.authenticationService,
mastodonEmojiService: context.mastodonEmojiService,
- dateTimeProvider: DateTimeSwiftProvider(),
- twitterTextProvider: OfficialTwitterTextProvider()
+ statusViewConfigureContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
)
coordinator.present(scene: .compose(viewModel: composeViewModel, contentViewModel: composeContentViewModel), from: self, transition: .modal(animated: true, completion: nil))
diff --git a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift
index 0f0dc76f..5a53dc95 100644
--- a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift
@@ -7,6 +7,7 @@
//
import UIKit
+import AppShared
extension HashtagTimelineViewModel {
func setupDiffableDataSource(
@@ -15,7 +16,12 @@ extension HashtagTimelineViewModel {
) {
let configuration = StatusSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- timelineMiddleLoaderTableViewCellDelegate: nil
+ timelineMiddleLoaderTableViewCellDelegate: nil,
+ statusViewConfigurationContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
diff --git a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift
index 7b452750..375cbd06 100644
--- a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift
+++ b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift
@@ -71,7 +71,7 @@ extension HashtagTimelineViewModel.State {
}
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel.swift b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel.swift
index 4295756a..f5d40fd6 100644
--- a/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel.swift
+++ b/TwidereX/Scene/HashtagTimeline/HashtagTimelineViewModel.swift
@@ -43,7 +43,7 @@ final class HashtagTimelineViewModel {
self.statusRecordFetchedResultController = StatusRecordFetchedResultController(managedObjectContext: context.managedObjectContext)
// end init
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.map { $0?.userIdentifier }
.assign(to: &statusRecordFetchedResultController.$userIdentifier)
}
diff --git a/TwidereX/Scene/MainTab/MainTabBarController.swift b/TwidereX/Scene/MainTab/MainTabBarController.swift
index ef91ac75..a745dd0e 100644
--- a/TwidereX/Scene/MainTab/MainTabBarController.swift
+++ b/TwidereX/Scene/MainTab/MainTabBarController.swift
@@ -43,14 +43,19 @@ class MainTabBarController: UITabBarController {
var image: UIImage {
switch self {
- case .home:
- return Asset.ObjectTools.house.image.withRenderingMode(.alwaysTemplate)
- case .notification:
- return Asset.ObjectTools.bell.image.withRenderingMode(.alwaysTemplate)
- case .search:
- return Asset.ObjectTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate)
- case .me:
- return Asset.Human.person.image.withRenderingMode(.alwaysTemplate)
+ case .home: return Asset.ObjectTools.house.image.withRenderingMode(.alwaysTemplate)
+ case .notification: return Asset.ObjectTools.bell.image.withRenderingMode(.alwaysTemplate)
+ case .search: return Asset.ObjectTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate)
+ case .me: return Asset.Human.person.image.withRenderingMode(.alwaysTemplate)
+ }
+ }
+
+ var largeImage: UIImage {
+ switch self {
+ case .home: return Asset.ObjectTools.houseLarge.image.withRenderingMode(.alwaysTemplate)
+ case .notification: return Asset.ObjectTools.bellLarge.image.withRenderingMode(.alwaysTemplate)
+ case .search: return Asset.ObjectTools.magnifyingglassLarge.image.withRenderingMode(.alwaysTemplate)
+ case .me: return Asset.Human.personLarge.image.withRenderingMode(.alwaysTemplate)
}
}
@@ -110,18 +115,16 @@ extension MainTabBarController {
let tabs = Tab.allCases
let viewControllers: [UIViewController] = tabs.map { tab in
let viewController = tab.viewController(context: context, coordinator: coordinator)
- viewController.tabBarItem.title = "" // set text to empty string for image only style (SDK failed to layout when set to nil)
+ viewController.tabBarItem.title = tab.title
viewController.tabBarItem.image = tab.image
+ viewController.tabBarItem.accessibilityLabel = tab.title
+ viewController.tabBarItem.largeContentSizeImage = tab.largeImage
+
return viewController
}
setViewControllers(viewControllers, animated: false)
selectedIndex = 0
-// // TODO: custom accent color
-// let tabBarAppearance = UITabBarAppearance()
-// tabBarAppearance.configureWithDefaultBackground()
-// tabBar.standardAppearance = tabBarAppearance
-
let feedbackGenerator = UINotificationFeedbackGenerator()
context.publisherService.statusPublishResult
diff --git a/TwidereX/Scene/MediaPreview/Image/MediaPreviewImageView.swift b/TwidereX/Scene/MediaPreview/Image/MediaPreviewImageView.swift
index d1c47c56..831bdb0a 100644
--- a/TwidereX/Scene/MediaPreview/Image/MediaPreviewImageView.swift
+++ b/TwidereX/Scene/MediaPreview/Image/MediaPreviewImageView.swift
@@ -17,6 +17,8 @@ final class MediaPreviewImageView: UIScrollView {
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = true
+ imageView.accessibilityIgnoresInvertColors = true
+ imageView.isAccessibilityElement = true
return imageView
}()
diff --git a/TwidereX/Scene/MediaPreview/MediaPreviewViewController.swift b/TwidereX/Scene/MediaPreview/MediaPreviewViewController.swift
index 0102da14..271980ba 100644
--- a/TwidereX/Scene/MediaPreview/MediaPreviewViewController.swift
+++ b/TwidereX/Scene/MediaPreview/MediaPreviewViewController.swift
@@ -54,6 +54,7 @@ final class MediaPreviewViewController: UIViewController, NeedsDependency {
let button = HitTestExpandedButton(type: .system)
button.imageView?.tintColor = .label
button.setImage(Asset.Editing.xmarkRound.image.withRenderingMode(.alwaysTemplate), for: .normal)
+ button.accessibilityLabel = L10n.Accessibility.Common.close
return button
}()
@@ -132,53 +133,14 @@ extension MediaPreviewViewController {
pageControl.trailingAnchor.constraint(equalTo: pageControlBackgroundVisualEffectView.trailingAnchor),
pageControl.bottomAnchor.constraint(equalTo: pageControlBackgroundVisualEffectView.bottomAnchor),
])
-
-//
-// switch viewModel.rootItem {
-// case .tweet(let meta):
-// pageControl.numberOfPages = viewModel.viewControllers.count
-// if case let .tweet(meta) = viewModel.rootItem {
-// pageControl.currentPage = meta.initialIndex
-// }
-// pageControl.isHidden = viewModel.viewControllers.count == 1
-// mediaInfoDescriptionView.actionToolbarContainer.delegate = self
-// let managedObjectContext = self.context.managedObjectContext
-// managedObjectContext.perform {
-// let tweet = managedObjectContext.object(with: meta.tweetObjectID) as! Tweet
-// let targetTweet = tweet.retweet ?? tweet
-// let activeTwitterAuthenticationBox = self.context.authenticationService.activeTwitterAuthenticationBox.value
-// let requestTwitterUserID = activeTwitterAuthenticationBox?.twitterUserID ?? ""
-// MediaPreviewViewController.configure(actionToolbarContainer: self.mediaInfoDescriptionView.actionToolbarContainer, tweet: targetTweet, requestTwitterUserID: requestTwitterUserID)
-//
-// // observe model change
-// ManagedObjectObserver.observe(object: tweet.retweet ?? tweet)
-// .receive(on: DispatchQueue.main)
-// .sink { _ in
-// // do nothing
-// } receiveValue: { [weak self] change in
-// guard let self = self else { return }
-// guard case let .update(object) = change.changeType,
-// let newTweet = object as? Tweet else { return }
-// let targetTweet = newTweet.retweet ?? newTweet
-// let activeTwitterAuthenticationBox = self.context.authenticationService.activeTwitterAuthenticationBox.value
-// let requestTwitterUserID = activeTwitterAuthenticationBox?.twitterUserID ?? ""
-//
-// MediaPreviewViewController.configure(actionToolbarContainer: self.mediaInfoDescriptionView.actionToolbarContainer, tweet: targetTweet, requestTwitterUserID: requestTwitterUserID)
-// }
-// .store(in: &self.disposeBag)
-// }
-// case .local(let meta):
-// pageControl.isHidden = true
-// mediaInfoDescriptionView.isHidden = true
-// }
if let status = viewModel.status {
mediaInfoDescriptionView.configure(
statusObject: status,
- configurationContext: MediaInfoDescriptionView.ConfigurationContext(
+ configurationContext: .init(
dateTimeProvider: DateTimeSwiftProvider(),
twitterTextProvider: OfficialTwitterTextProvider(),
- activeAuthenticationContext: context.authenticationService.activeAuthenticationContext.eraseToAnyPublisher()
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
)
)
} else {
@@ -188,6 +150,7 @@ extension MediaPreviewViewController {
pageControl.numberOfPages = viewModel.viewControllers.count
pageControl.isHidden = viewModel.viewControllers.count == 1
pageControl.isUserInteractionEnabled = false
+ pageControl.addTarget(self, action: #selector(MediaPreviewViewController.pageControlValueDidChanged(_:)), for: .valueChanged)
viewModel.mediaPreviewImageViewControllerDelegate = self
@@ -214,6 +177,17 @@ extension MediaPreviewViewController {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
dismiss(animated: true, completion: nil)
}
+
+ @objc private func pageControlValueDidChanged(_ sender: UIPageControl) {
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
+ let currentPage = sender.currentPage
+ guard let pageCount = pageViewController.pageCount,
+ currentPage >= 0,
+ currentPage < pageCount
+ else { return }
+
+ pageViewController.scrollToPage(.at(index: currentPage), animated: true, completion: nil)
+ }
}
diff --git a/TwidereX/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift b/TwidereX/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift
index 1aedef78..a3bdb2fb 100644
--- a/TwidereX/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift
+++ b/TwidereX/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift
@@ -65,5 +65,4 @@ extension MediaPreviewVideoViewModel {
let assetURL: URL?
let previewURL: URL?
}
-
}
diff --git a/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView+ViewModel.swift b/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView+ViewModel.swift
index 6b74de02..3559651b 100644
--- a/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView+ViewModel.swift
+++ b/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView+ViewModel.swift
@@ -9,8 +9,9 @@
import os.log
import UIKit
import Combine
-import AppShared
import CoreDataStack
+import AppShared
+import TwidereCore
import TwitterMeta
import MastodonMeta
import Meta
@@ -19,31 +20,84 @@ extension MediaInfoDescriptionView {
final class ViewModel: ObservableObject {
var disposeBag = Set()
var observations = Set()
+
+ @Published public var platform: Platform = .none
+ @Published public var twitterTextProvider: TwitterTextProvider?
+ @Published public var dateTimeProvider: DateTimeProvider?
+ @Published public var authenticationContext: AuthenticationContext?
+ @Published public var authorUserIdentifier: UserIdentifier?
@Published public var authorAvatarImageURL: URL?
@Published public var authorName: MetaContent?
- @Published public var protected: Bool = false
+ @Published public var protected = false
+ @Published public var isMyself = false
@Published public var content: MetaContent?
+
+ @Published public var visibility: StatusVisibility?
- @Published public var isRepost: Bool = false
- @Published public var isLike: Bool = false
+ @Published public var isRepost = false
+ @Published public var isRepostEnabled = true
+
+ @Published public var isLike = false
+
+ init() {
+ // isMyself
+ Publishers.CombineLatest(
+ $authenticationContext,
+ $authorUserIdentifier
+ )
+ .map { authenticationContext, authorUserIdentifier -> Bool in
+ guard let authenticationContext = authenticationContext,
+ let authorUserIdentifier = authorUserIdentifier
+ else { return false }
+ let meUserIdentifier = authenticationContext.userIdentifier
+ switch (meUserIdentifier, authorUserIdentifier) {
+ case (.twitter(let me), .twitter(let author)):
+ return me.id == author.id
+ case (.mastodon(let me), .mastodon(let author)):
+ return me.domain == author.domain
+ && me.id == author.id
+ default:
+ return false
+ }
+ }
+ .assign(to: &$isMyself)
+ // isRepostEnabled
+ Publishers.CombineLatest4(
+ $platform,
+ $protected,
+ $isMyself,
+ $visibility
+ )
+ .map { platform, protected, isMyself, visibility -> Bool in
+ switch platform {
+ case .none:
+ return true
+ case .twitter:
+ guard !isMyself else { return true }
+ return !protected
+ case .mastodon:
+ guard !isMyself else { return true }
+ guard case let .mastodon(visibility) = visibility else {
+ return true
+ }
+ switch visibility {
+ case .public, .unlisted:
+ return true
+ case .private, .direct, ._other:
+ return false
+ }
+ }
+ }
+ .assign(to: &$isRepostEnabled)
+ }
}
}
extension MediaInfoDescriptionView.ViewModel {
func bind(view: MediaInfoDescriptionView) {
- // content
- $content
- .sink { metaContent in
- guard let content = metaContent else {
- view.contentTextView.reset()
- return
- }
- view.contentTextView.configure(content: content)
- }
- .store(in: &disposeBag)
// avatar
$authorAvatarImageURL
.sink { url in
@@ -75,18 +129,31 @@ extension MediaInfoDescriptionView.ViewModel {
view.nameMetaLabel.configure(content: metaContent)
}
.store(in: &disposeBag)
+ // content
+ $content
+ .sink { metaContent in
+ guard let content = metaContent else {
+ view.contentTextView.reset()
+ return
+ }
+ view.contentTextView.configure(content: content)
+ }
+ .store(in: &disposeBag)
// toolbar
+ $platform
+ .assign(to: \.platform, on: view.toolbar.viewModel)
+ .store(in: &disposeBag)
Publishers.CombineLatest(
$isRepost,
- $protected
+ $isRepostEnabled
)
- .sink { isRepost, protected in
- view.toolbar.setupRepost(count: 0, isRepost: isRepost, isLocked: protected)
+ .sink { isRepost, isEnabled in
+ view.toolbar.setupRepost(count: 0, isEnabled: isEnabled, isHighlighted: isRepost)
}
.store(in: &disposeBag)
$isLike
.sink { isLike in
- view.toolbar.setupLike(count: 0, isLike: isLike)
+ view.toolbar.setupLike(count: 0, isHighlighted: isLike)
}
.store(in: &disposeBag)
}
@@ -94,24 +161,9 @@ extension MediaInfoDescriptionView.ViewModel {
extension MediaInfoDescriptionView {
- public struct ConfigurationContext {
- public let dateTimeProvider: DateTimeProvider
- public let twitterTextProvider: TwitterTextProvider
- public let activeAuthenticationContext: AnyPublisher
-
- public init(
- dateTimeProvider: DateTimeProvider,
- twitterTextProvider: TwitterTextProvider,
- activeAuthenticationContext: AnyPublisher
- ) {
- self.dateTimeProvider = dateTimeProvider
- self.twitterTextProvider = twitterTextProvider
- self.activeAuthenticationContext = activeAuthenticationContext
- }
- }
+ public typealias ConfigurationContext = StatusView.ConfigurationContext
}
-
extension MediaInfoDescriptionView {
public func configure(
statusObject object: StatusObject,
@@ -137,24 +189,17 @@ extension MediaInfoDescriptionView {
twitterStatus status: TwitterStatus,
configurationContext: ConfigurationContext
) {
- configureAuthor(
- twitterStatus: status,
- dateTimeProvider: configurationContext.dateTimeProvider
- )
- configureContent(
- twitterStatus: status,
- twitterTextProvider: configurationContext.twitterTextProvider
- )
- configureToolbar(
- twitterStatus: status,
- activeAuthenticationContext: configurationContext.activeAuthenticationContext
- )
+ viewModel.platform = .twitter
+ viewModel.dateTimeProvider = configurationContext.dateTimeProvider
+ viewModel.twitterTextProvider = configurationContext.twitterTextProvider
+ configurationContext.authenticationContext.assign(to: \.authenticationContext, on: viewModel).store(in: &disposeBag)
+
+ configureAuthor(twitterStatus: status)
+ configureContent(twitterStatus: status)
+ configureToolbar(twitterStatus: status)
}
- private func configureAuthor(
- twitterStatus status: TwitterStatus,
- dateTimeProvider: DateTimeProvider
- ) {
+ private func configureAuthor(twitterStatus status: TwitterStatus) {
let author = (status.repost ?? status).author
// author avatar
@@ -173,10 +218,12 @@ extension MediaInfoDescriptionView {
.store(in: &disposeBag)
}
- private func configureContent(
- twitterStatus status: TwitterStatus,
- twitterTextProvider: TwitterTextProvider
- ) {
+ private func configureContent(twitterStatus status: TwitterStatus) {
+ guard let twitterTextProvider = viewModel.twitterTextProvider else {
+ assertionFailure()
+ return
+ }
+
let status = status.repost ?? status
let content = TwitterContent(content: status.text)
let metaContent = TwitterMetaContent.convert(
@@ -185,17 +232,15 @@ extension MediaInfoDescriptionView {
twitterTextProvider: twitterTextProvider
)
viewModel.content = metaContent
+ viewModel.visibility = nil
}
- private func configureToolbar(
- twitterStatus status: TwitterStatus,
- activeAuthenticationContext: AnyPublisher
- ) {
+ private func configureToolbar(twitterStatus status: TwitterStatus) {
let status = status.repost ?? status
// relationship
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.repostBy)
)
.map { authenticationContext, repostBy in
@@ -209,7 +254,7 @@ extension MediaInfoDescriptionView {
.store(in: &disposeBag)
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.likeBy)
)
.map { authenticationContext, likeBy in
@@ -230,20 +275,19 @@ extension MediaInfoDescriptionView {
mastodonStatus status: MastodonStatus,
configurationContext: ConfigurationContext
) {
+ viewModel.platform = .mastodon
+ viewModel.dateTimeProvider = configurationContext.dateTimeProvider
+ viewModel.twitterTextProvider = configurationContext.twitterTextProvider
+ configurationContext.authenticationContext.assign(to: \.authenticationContext, on: viewModel).store(in: &disposeBag)
+
// configureHeader(mastodonStatus: status, mastodonNotification: notification)
- configureAuthor(mastodonStatus: status, dateTimeProvider: configurationContext.dateTimeProvider)
+ configureAuthor(mastodonStatus: status)
configureContent(mastodonStatus: status)
// configureMedia(mastodonStatus: status)
- configureToolbar(
- mastodonStatus: status,
- activeAuthenticationContext: configurationContext.activeAuthenticationContext
- )
+ configureToolbar(mastodonStatus: status)
}
- private func configureAuthor(
- mastodonStatus status: MastodonStatus,
- dateTimeProvider: DateTimeProvider
- ) {
+ private func configureAuthor(mastodonStatus status: MastodonStatus) {
let author = (status.repost ?? status).author
// author avatar
@@ -285,17 +329,16 @@ extension MediaInfoDescriptionView {
assertionFailure(error.localizedDescription)
viewModel.content = PlaintextMetaContent(string: "")
}
+
+ viewModel.visibility = status.visibility.asStatusVisibility
}
- private func configureToolbar(
- mastodonStatus status: MastodonStatus,
- activeAuthenticationContext: AnyPublisher
- ) {
+ private func configureToolbar(mastodonStatus status: MastodonStatus) {
let status = status.repost ?? status
// relationship
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.repostBy)
)
.map { authenticationContext, repostBy in
@@ -310,7 +353,7 @@ extension MediaInfoDescriptionView {
.store(in: &disposeBag)
Publishers.CombineLatest(
- activeAuthenticationContext,
+ viewModel.$authenticationContext,
status.publisher(for: \.likeBy)
)
.map { authenticationContext, likeBy in
diff --git a/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView.swift b/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView.swift
index 059c9284..e3216b54 100644
--- a/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView.swift
+++ b/TwidereX/Scene/MediaPreview/View/MediaInfoDescriptionView.swift
@@ -38,7 +38,10 @@ final class MediaInfoDescriptionView: UIView {
let avatarView: ProfileAvatarView = {
let avatarView = ProfileAvatarView()
- avatarView.dimension = 32
+ avatarView.setup(dimension: .inline)
+ avatarView.isAccessibilityElement = true
+ avatarView.accessibilityLabel = L10n.Accessibility.Common.Status.authorAvatar
+ avatarView.accessibilityHint = L10n.Accessibility.VoiceOver.doubleTapToOpenProfile
return avatarView
}()
@@ -114,7 +117,12 @@ extension MediaInfoDescriptionView {
bottomContainerStackView.spacing = 8
bottomContainerStackView.alignment = .center
+ avatarView.translatesAutoresizingMaskIntoConstraints = false
bottomContainerStackView.addArrangedSubview(avatarView)
+ NSLayoutConstraint.activate([
+ avatarView.widthAnchor.constraint(equalToConstant: MediaInfoDescriptionView.avatarImageViewSize.width).priority(.required - 1),
+ avatarView.heightAnchor.constraint(equalToConstant: MediaInfoDescriptionView.avatarImageViewSize.height).priority(.required - 1),
+ ])
bottomContainerStackView.addArrangedSubview(nameMetaLabel)
toolbar.translatesAutoresizingMaskIntoConstraints = false
bottomContainerStackView.addArrangedSubview(toolbar)
@@ -168,6 +176,20 @@ extension MediaInfoDescriptionView: StatusToolbarDelegate {
}
}
+extension MediaInfoDescriptionView {
+ override var accessibilityElements: [Any]? {
+ get {
+ return [
+ avatarView,
+ nameMetaLabel,
+ toolbar,
+ ]
+ }
+ set { }
+ }
+
+}
+
#if DEBUG
import SwiftUI
diff --git a/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift b/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift
index 31f359ab..46b2b6a8 100644
--- a/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift
@@ -12,6 +12,7 @@ import CoreData
import CoreDataStack
import TwitterSDK
import MastodonSDK
+import AppShared
extension NotificationTimelineViewModel {
@@ -22,7 +23,12 @@ extension NotificationTimelineViewModel {
) {
let configuration = NotificationSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- userTableViewCellDelegate: userTableViewCellDelegate
+ userTableViewCellDelegate: userTableViewCellDelegate,
+ statusViewConfigurationContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
diffableDataSource = NotificationSection.diffableDataSource(
tableView: tableView,
@@ -123,7 +129,7 @@ extension NotificationTimelineViewModel {
// load lastest
func loadLatest() async {
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
do {
switch authenticationContext {
case .twitter(let authenticationContext):
@@ -158,7 +164,7 @@ extension NotificationTimelineViewModel {
// load timeline gap
func loadMore(item: NotificationItem) async {
guard case let .feedLoader(record) = item else { return }
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
let managedObjectContext = context.managedObjectContext
let key = "LoadMore@\(record.objectID)"
diff --git a/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift b/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift
index 3cae69f2..3e3feee0 100644
--- a/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift
+++ b/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift
@@ -48,7 +48,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
super.didEnter(from: previousState)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift
index 9548b354..d2ad5462 100644
--- a/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift
+++ b/TwidereX/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift
@@ -48,7 +48,7 @@ final class NotificationTimelineViewModel {
self.fetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
// end init
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
let emptyFeedPredicate = Feed.nonePredicate()
diff --git a/TwidereX/Scene/Notification/NotificationViewController.swift b/TwidereX/Scene/Notification/NotificationViewController.swift
index 91a7b02e..5e047a8d 100644
--- a/TwidereX/Scene/Notification/NotificationViewController.swift
+++ b/TwidereX/Scene/Notification/NotificationViewController.swift
@@ -60,9 +60,11 @@ extension NotificationViewController {
navigationItem.leftBarButtonItem = avatarBarButtonItem
avatarBarButtonItem.avatarButton.addTarget(self, action: #selector(NotificationViewController.avatarButtonPressed(_:)), for: .touchUpInside)
+ avatarBarButtonItem.delegate = self
+
Publishers.CombineLatest(
- context.authenticationService.activeAuthenticationContext,
- viewModel.viewDidAppear.eraseToAnyPublisher()
+ context.authenticationService.$activeAuthenticationContext,
+ viewModel.viewDidAppear
)
.receive(on: DispatchQueue.main)
.sink { [weak self] authenticationContext, _ in
@@ -174,3 +176,7 @@ extension NotificationViewController {
scrollToPage(.at(index: index), animated: true, completion: nil)
}
}
+
+// MARK: - AvatarBarButtonItemDelegate
+extension NotificationViewController: AvatarBarButtonItemDelegate { }
+
diff --git a/TwidereX/Scene/Notification/NotificationViewModel.swift b/TwidereX/Scene/Notification/NotificationViewModel.swift
index d6f41c19..f70f8f0d 100644
--- a/TwidereX/Scene/Notification/NotificationViewModel.swift
+++ b/TwidereX/Scene/Notification/NotificationViewModel.swift
@@ -31,7 +31,7 @@ final class NotificationViewModel {
self._coordinator = coordinator
// end init
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.receive(on: DispatchQueue.main)
.sink { [weak self] authenticationContext in
guard let self = self else { return }
diff --git a/TwidereX/Scene/Onboarding/Welcome/WelcomeView.swift b/TwidereX/Scene/Onboarding/Welcome/WelcomeView.swift
index 4dade20d..46c64c52 100644
--- a/TwidereX/Scene/Onboarding/Welcome/WelcomeView.swift
+++ b/TwidereX/Scene/Onboarding/Welcome/WelcomeView.swift
@@ -10,6 +10,7 @@ import os.log
import SwiftUI
import Introspect
import TwidereAsset
+import TwidereLocalization
struct WelcomeView: View {
@@ -32,7 +33,7 @@ struct WelcomeView: View {
HStack {
// not use UIImage init method here
// only .init(_:bundle:) works with the dynamic Dark Mode asset
- Image(Asset.Scene.Welcome.twidere.name, bundle: TwidereAsset.bundle)
+ Image(decorative: Asset.Scene.Welcome.twidere.name, bundle: TwidereAsset.bundle)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 48, height: 48)
@@ -74,6 +75,7 @@ struct WelcomeView: View {
}
}
)
+ .accessibilityHint(L10n.Accessibility.Scene.SignIn.pleaseEnterMastodonDomainToSignIn)
.textFieldStyle(PlainTextFieldStyle())
.autocapitalization(.none)
.disableAutocorrection(true)
@@ -129,7 +131,7 @@ struct TwitterAuthenticateButton: View {
primaryAction()
} label: {
HStack {
- Image(Asset.Logo.twitter.name, bundle: TwidereAsset.bundle)
+ Image(decorative: Asset.Logo.twitter.name, bundle: TwidereAsset.bundle)
.renderingMode(.template)
Spacer()
if isBusy {
@@ -147,9 +149,12 @@ struct TwitterAuthenticateButton: View {
Button {
secondaryAction()
} label: {
- Image(Asset.Editing.ellipsis.name, bundle: TwidereAsset.bundle)
- .renderingMode(.template)
- .padding(.horizontal, 18)
+ Image(
+ Asset.Editing.ellipsis.name, bundle: TwidereAsset.bundle,
+ label: Text(L10n.Accessibility.Scene.SignIn.twitterClientAuthenticationKeySetting)
+ )
+ .renderingMode(.template)
+ .padding(.horizontal, 18)
}
}
.frame(height: 48)
@@ -171,7 +176,7 @@ struct MastodonAuthenticateButton: View {
primaryAction()
} label: {
HStack {
- Image(Asset.Logo.mastodon.name, bundle: TwidereAsset.bundle)
+ Image(decorative: Asset.Logo.mastodon.name, bundle: TwidereAsset.bundle)
.renderingMode(.template)
Spacer()
if isBusy {
@@ -183,7 +188,7 @@ struct MastodonAuthenticateButton: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer()
- Image(Asset.Arrows.arrowRight.name, bundle: TwidereAsset.bundle)
+ Image(decorative: Asset.Arrows.arrowRight.name, bundle: TwidereAsset.bundle)
.renderingMode(.template)
}
.padding(.horizontal, 18)
diff --git a/TwidereX/Scene/Onboarding/Welcome/WelcomeViewController.swift b/TwidereX/Scene/Onboarding/Welcome/WelcomeViewController.swift
index 077bea04..38c64b61 100644
--- a/TwidereX/Scene/Onboarding/Welcome/WelcomeViewController.swift
+++ b/TwidereX/Scene/Onboarding/Welcome/WelcomeViewController.swift
@@ -20,7 +20,7 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
- let logger = Logger(subsystem: "WelcomeViewController", category: "UI")
+ let logger = Logger(subsystem: "WelcomeViewController", category: "ViewController")
var disposeBag = Set()
var viewModel: WelcomeViewModel!
@@ -28,12 +28,17 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
private var twitterAuthenticationController: TwitterAuthenticationController?
private var mastodonAuthenticationController: MastodonAuthenticationController?
- private(set) lazy var closeBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(WelcomeViewController.closeBarButtonItemPressed(_:)))
+ private(set) lazy var closeBarButtonItem: UIBarButtonItem = {
+ let item = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(WelcomeViewController.closeBarButtonItemPressed(_:)))
+ item.accessibilityLabel = L10n.Accessibility.Common.close
+ return item
+ }()
private(set) lazy var backBarButtonItem: UIBarButtonItem = {
let image = Asset.Arrows.arrowLeft.image.withRenderingMode(.alwaysTemplate)
let item = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(WelcomeViewController.backBarButtonItemPressed(_:)))
item.tintColor = .label
+ item.accessibilityLabel = L10n.Accessibility.Common.back
return item
}()
diff --git a/TwidereX/Scene/Profile/FriendshipList/FollowingListViewModel+State.swift b/TwidereX/Scene/Profile/FriendshipList/FollowingListViewModel+State.swift
index c4c8a198..14fdbe1a 100644
--- a/TwidereX/Scene/Profile/FriendshipList/FollowingListViewModel+State.swift
+++ b/TwidereX/Scene/Profile/FriendshipList/FollowingListViewModel+State.swift
@@ -55,7 +55,7 @@ extension FriendshipListViewModel.State {
super.didEnter(from: previousState)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Profile/FriendshipList/FriendshipListViewModel.swift b/TwidereX/Scene/Profile/FriendshipList/FriendshipListViewModel.swift
index 7770b0bd..295ffa92 100644
--- a/TwidereX/Scene/Profile/FriendshipList/FriendshipListViewModel.swift
+++ b/TwidereX/Scene/Profile/FriendshipList/FriendshipListViewModel.swift
@@ -63,7 +63,7 @@ final class FriendshipListViewModel: NSObject {
context: AppContext,
kind: Kind
) {
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return nil }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return nil }
let userIdentifier = authenticationContext.userIdentifier
self.init(
diff --git a/TwidereX/Scene/Profile/Header/View/ProfileHeaderView.swift b/TwidereX/Scene/Profile/Header/View/ProfileHeaderView.swift
index 18966e99..b5011e77 100644
--- a/TwidereX/Scene/Profile/Header/View/ProfileHeaderView.swift
+++ b/TwidereX/Scene/Profile/Header/View/ProfileHeaderView.swift
@@ -52,7 +52,7 @@ final class ProfileHeaderView: UIView {
let avatarView: ProfileAvatarView = {
let avatarView = ProfileAvatarView()
- avatarView.dimension = ProfileHeaderView.avatarViewSize.width
+ avatarView.setup(dimension: .plain)
return avatarView
}()
@@ -135,6 +135,8 @@ extension ProfileHeaderView {
NSLayoutConstraint.activate([
avatarView.centerXAnchor.constraint(equalTo: centerXAnchor),
avatarView.centerYAnchor.constraint(equalTo: bannerContainer.bottomAnchor),
+ avatarView.widthAnchor.constraint(equalToConstant: ProfileHeaderView.avatarViewSize.width).priority(.required - 1),
+ avatarView.heightAnchor.constraint(equalToConstant: ProfileHeaderView.avatarViewSize.height).priority(.required - 1),
])
// container: V - [ name container | usernameLabel | friendshipButton | bioTextAreaView | … ]
diff --git a/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewController.swift b/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewController.swift
index c74438bd..fee011c1 100644
--- a/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewController.swift
+++ b/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewController.swift
@@ -179,29 +179,6 @@ extension UserTimelineViewController: UITableViewDelegate, AutoGenerateTableView
}
-//// MARK: - AVPlayerViewControllerDelegate
-//extension UserTimelineViewController: AVPlayerViewControllerDelegate {
-//
-// func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
-// handlePlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator)
-// }
-//
-// func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
-// handlePlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator)
-// }
-//
-//}
-
-// MARK: - TimelinePostTableViewCellDelegate
-//extension UserTimelineViewController: TimelinePostTableViewCellDelegate {
-// weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self }
-// func parent() -> UIViewController { return self }
-//}
-//
-//// MARK: - TimelineHeaderTableViewCellDelegate
-//extension UserTimelineViewController: TimelineHeaderTableViewCellDelegate { }
-
-
// MARK: - CustomScrollViewContainerController
extension UserTimelineViewController: ScrollViewContainer {
var scrollView: UIScrollView { return tableView }
diff --git a/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+Diffable.swift b/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+Diffable.swift
index 797782d1..892ce4c0 100644
--- a/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+Diffable.swift
@@ -11,6 +11,7 @@ import UIKit
import Combine
import CoreData
import CoreDataStack
+import AppShared
extension UserTimelineViewModel {
func setupDiffableDataSource(
@@ -19,7 +20,12 @@ extension UserTimelineViewModel {
) {
let configuration = StatusSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- timelineMiddleLoaderTableViewCellDelegate: nil
+ timelineMiddleLoaderTableViewCellDelegate: nil,
+ statusViewConfigurationContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
diff --git a/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+State.swift b/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+State.swift
index 6f977ea6..13b742f7 100644
--- a/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+State.swift
+++ b/TwidereX/Scene/Profile/HomeTimeline/UserTimelineViewModel+State.swift
@@ -176,7 +176,7 @@ extension UserTimelineViewModel.State {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
guard let userIdentifier = viewModel.userIdentifier,
- let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+Diffable.swift b/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+Diffable.swift
index 5524dc00..f47bfa2c 100644
--- a/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+Diffable.swift
@@ -10,6 +10,7 @@ import os.log
import UIKit
import CoreData
import CoreDataStack
+import AppShared
extension UserLikeTimelineViewModel {
func setupDiffableDataSource(
@@ -18,8 +19,14 @@ extension UserLikeTimelineViewModel {
) {
let configuration = StatusSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- timelineMiddleLoaderTableViewCellDelegate: nil
+ timelineMiddleLoaderTableViewCellDelegate: nil,
+ statusViewConfigurationContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
+
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
diff --git a/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+State.swift b/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+State.swift
index eb8bdd43..380de235 100644
--- a/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+State.swift
+++ b/TwidereX/Scene/Profile/LikeTimeline/UserLikeTimelineViewModel+State.swift
@@ -163,7 +163,7 @@ extension UserLikeTimelineViewModel.State {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
guard let userIdentifier = viewModel.userIdentifier,
- let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Profile/MeProfileViewModel.swift b/TwidereX/Scene/Profile/MeProfileViewModel.swift
index c452d9fb..44a1cf59 100644
--- a/TwidereX/Scene/Profile/MeProfileViewModel.swift
+++ b/TwidereX/Scene/Profile/MeProfileViewModel.swift
@@ -17,7 +17,7 @@ final class MeProfileViewModel: ProfileViewModel {
override init(context: AppContext) {
super.init(context: context)
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
Task {
diff --git a/TwidereX/Scene/Profile/MediaTimeline/UserMediaTimelineViewModel+State.swift b/TwidereX/Scene/Profile/MediaTimeline/UserMediaTimelineViewModel+State.swift
index ec07b526..93498762 100644
--- a/TwidereX/Scene/Profile/MediaTimeline/UserMediaTimelineViewModel+State.swift
+++ b/TwidereX/Scene/Profile/MediaTimeline/UserMediaTimelineViewModel+State.swift
@@ -168,7 +168,7 @@ extension UserMediaTimelineViewModel.State {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
guard let userIdentifier = viewModel.userIdentifier,
- let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Profile/ProfileViewController.swift b/TwidereX/Scene/Profile/ProfileViewController.swift
index f04ec05f..aface37c 100644
--- a/TwidereX/Scene/Profile/ProfileViewController.swift
+++ b/TwidereX/Scene/Profile/ProfileViewController.swift
@@ -122,10 +122,11 @@ extension ProfileViewController {
if navigationController?.viewControllers.first == self {
navigationItem.leftBarButtonItem = avatarBarButtonItem
avatarBarButtonItem.avatarButton.addTarget(self, action: #selector(ProfileViewController.avatarButtonPressed(_:)), for: .touchUpInside)
+ avatarBarButtonItem.delegate = self
Publishers.CombineLatest(
- context.authenticationService.activeAuthenticationContext,
- viewModel.viewDidAppear.eraseToAnyPublisher()
+ context.authenticationService.$activeAuthenticationContext,
+ viewModel.viewDidAppear
)
.receive(on: DispatchQueue.main)
.sink { [weak self] authenticationContext, _ in
@@ -181,7 +182,7 @@ extension ProfileViewController {
Publishers.CombineLatest3(
viewModel.relationshipViewModel.$optionSet, // update trigger
viewModel.$userRecord,
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
)
.receive(on: DispatchQueue.main)
.sink { [weak self] optionSet, userRecord, authenticationContext in
@@ -292,8 +293,11 @@ extension ProfileViewController {
apiService: context.apiService,
authenticationService: context.authenticationService,
mastodonEmojiService: context.mastodonEmojiService,
- dateTimeProvider: DateTimeSwiftProvider(),
- twitterTextProvider: OfficialTwitterTextProvider()
+ statusViewConfigureContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
)
coordinator.present(scene: .compose(viewModel: composeViewModel, contentViewModel: composeContentViewModel), from: self, transition: .modal(animated: true, completion: nil))
@@ -301,12 +305,15 @@ extension ProfileViewController {
}
+// MARK: - AvatarBarButtonItemDelegate
+extension ProfileViewController: AvatarBarButtonItemDelegate { }
+
// MARK: - ProfileHeaderViewControllerDelegate
extension ProfileViewController: ProfileHeaderViewControllerDelegate {
func headerViewController(_ viewController: ProfileHeaderViewController, profileHeaderView: ProfileHeaderView, friendshipButtonDidPressed button: UIButton) {
guard let user = viewModel.user else { return }
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
guard let relationshipOptionSet = viewModel.relationshipViewModel.optionSet else { return }
let record = UserRecord(object: user)
diff --git a/TwidereX/Scene/Profile/ProfileViewModel.swift b/TwidereX/Scene/Profile/ProfileViewModel.swift
index e769fcdd..d62f86de 100644
--- a/TwidereX/Scene/Profile/ProfileViewModel.swift
+++ b/TwidereX/Scene/Profile/ProfileViewModel.swift
@@ -61,7 +61,7 @@ class ProfileViewModel: ObservableObject {
.assign(to: &$userIdentifier)
// bind active authentication
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
Task {
@@ -85,7 +85,7 @@ class ProfileViewModel: ObservableObject {
// observe friendship
Publishers.CombineLatest(
$userRecord,
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
)
.sink { [weak self] userRecord, authenticationContext in
guard let self = self else { return }
diff --git a/TwidereX/Scene/Profile/RemoteProfileViewModel.swift b/TwidereX/Scene/Profile/RemoteProfileViewModel.swift
index 0e419e41..10ad0226 100644
--- a/TwidereX/Scene/Profile/RemoteProfileViewModel.swift
+++ b/TwidereX/Scene/Profile/RemoteProfileViewModel.swift
@@ -25,7 +25,7 @@ final class RemoteProfileViewModel: ProfileViewModel {
setup(user: record)
case .twitter(let twitterContext):
Task {
- guard case let .twitter(authenticationContext) = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard case let .twitter(authenticationContext) = context.authenticationService.activeAuthenticationContext else { return }
do {
let _record = try await fetchTwitterUser(
twitterContext: twitterContext,
diff --git a/TwidereX/Scene/Search/SavedSearch/SavedSearchViewController.swift b/TwidereX/Scene/Search/SavedSearch/SavedSearchViewController.swift
index cdacf9bc..6127b121 100644
--- a/TwidereX/Scene/Search/SavedSearch/SavedSearchViewController.swift
+++ b/TwidereX/Scene/Search/SavedSearch/SavedSearchViewController.swift
@@ -81,7 +81,7 @@ extension SavedSearchViewController: UITableViewDelegate {
guard let diffableDataSource = self.viewModel.diffableDataSource,
case let .history(record) = diffableDataSource.itemIdentifier(for: indexPath),
- let authenticationContext = self.viewModel.context.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = self.viewModel.context.authenticationService.activeAuthenticationContext
else { return nil }
let deleteAction = UIContextualAction(
diff --git a/TwidereX/Scene/Search/SavedSearch/SavedSearchViewModel.swift b/TwidereX/Scene/Search/SavedSearch/SavedSearchViewModel.swift
index edd4d929..696bf352 100644
--- a/TwidereX/Scene/Search/SavedSearch/SavedSearchViewModel.swift
+++ b/TwidereX/Scene/Search/SavedSearch/SavedSearchViewModel.swift
@@ -32,7 +32,7 @@ final class SavedSearchViewModel {
self.savedSearchFetchedResultController = SavedSearchFetchedResultController(managedObjectContext: context.managedObjectContext)
// end init
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.map { $0?.userIdentifier }
.assign(to: \.userIdentifier, on: savedSearchFetchedResultController)
.store(in: &disposeBag)
diff --git a/TwidereX/Scene/Search/Search/SearchViewController.swift b/TwidereX/Scene/Search/Search/SearchViewController.swift
index 9998cecf..4fa78d69 100644
--- a/TwidereX/Scene/Search/Search/SearchViewController.swift
+++ b/TwidereX/Scene/Search/Search/SearchViewController.swift
@@ -123,7 +123,7 @@ extension SearchViewController {
Publishers.CombineLatest3(
viewModel.$savedSearchTexts,
searchResultViewModel.$searchText,
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
)
.receive(on: DispatchQueue.main)
.sink { [weak self] texts, searchText, activeAuthenticationContext in
@@ -235,7 +235,7 @@ extension SearchViewController: UITableViewDelegate {
guard let diffableDataSource = self.viewModel.diffableDataSource,
case let .history(record) = diffableDataSource.itemIdentifier(for: indexPath),
- let authenticationContext = self.viewModel.context.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = self.viewModel.context.authenticationService.activeAuthenticationContext
else { return nil }
let deleteAction = UIContextualAction(
diff --git a/TwidereX/Scene/Search/Search/SearchViewModel+Diffable.swift b/TwidereX/Scene/Search/Search/SearchViewModel+Diffable.swift
index 88c9b01c..79f0a30a 100644
--- a/TwidereX/Scene/Search/Search/SearchViewModel+Diffable.swift
+++ b/TwidereX/Scene/Search/Search/SearchViewModel+Diffable.swift
@@ -75,7 +75,7 @@ extension SearchViewModel {
Publishers.CombineLatest3(
historyItems,
trendItems,
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
)
.throttle(for: 0.3, scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] historyItems, trendItems, authenticationContext in
diff --git a/TwidereX/Scene/Search/Search/SearchViewModel.swift b/TwidereX/Scene/Search/Search/SearchViewModel.swift
index aaa8077f..30993614 100644
--- a/TwidereX/Scene/Search/Search/SearchViewModel.swift
+++ b/TwidereX/Scene/Search/Search/SearchViewModel.swift
@@ -37,7 +37,7 @@ final class SearchViewModel {
viewDidAppear
.sink { [weak self] _ in
guard let self = self else { return }
- guard let authenticationContext = self.context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = self.context.authenticationService.activeAuthenticationContext else { return }
Task {
do {
@@ -56,7 +56,7 @@ final class SearchViewModel {
)
.sink { [weak self] trendGroupIndex, _ in
guard let self = self else { return }
- guard let authenticationContext = self.context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = self.context.authenticationService.activeAuthenticationContext else { return }
Task {
do {
diff --git a/TwidereX/Scene/Search/SearchResult/Hashtag/SearchHashtagViewModel+State.swift b/TwidereX/Scene/Search/SearchResult/Hashtag/SearchHashtagViewModel+State.swift
index ca6f283b..64a158d1 100644
--- a/TwidereX/Scene/Search/SearchResult/Hashtag/SearchHashtagViewModel+State.swift
+++ b/TwidereX/Scene/Search/SearchResult/Hashtag/SearchHashtagViewModel+State.swift
@@ -71,7 +71,7 @@ extension SearchHashtagViewModel.State {
}
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Search/SearchResult/Media/SearchMediaViewModel+State.swift b/TwidereX/Scene/Search/SearchResult/Media/SearchMediaViewModel+State.swift
index dd5b5d72..dd3e923c 100644
--- a/TwidereX/Scene/Search/SearchResult/Media/SearchMediaViewModel+State.swift
+++ b/TwidereX/Scene/Search/SearchResult/Media/SearchMediaViewModel+State.swift
@@ -75,7 +75,7 @@ extension SearchMediaViewModel.State {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Search/SearchResult/SearchResultViewController.swift b/TwidereX/Scene/Search/SearchResult/SearchResultViewController.swift
index 64bab2e4..641a910f 100644
--- a/TwidereX/Scene/Search/SearchResult/SearchResultViewController.swift
+++ b/TwidereX/Scene/Search/SearchResult/SearchResultViewController.swift
@@ -126,7 +126,7 @@ extension SearchResultViewController: UISearchBarDelegate {
func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
guard let searchText = searchBar.text?.trimmingCharacters(in: .whitespacesAndNewlines), !searchText.isEmpty else { return }
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
Task {
try await DataSourceFacade.responseToCreateSavedSearch(
diff --git a/TwidereX/Scene/Search/SearchResult/SearchResultViewModel.swift b/TwidereX/Scene/Search/SearchResult/SearchResultViewModel.swift
index 5256cc96..ba2c8f6a 100644
--- a/TwidereX/Scene/Search/SearchResult/SearchResultViewModel.swift
+++ b/TwidereX/Scene/Search/SearchResult/SearchResultViewModel.swift
@@ -31,7 +31,7 @@ final class SearchResultViewModel {
self.context = context
self._coordinator = coordinator
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.receive(on: DispatchQueue.main)
.sink { [weak self] authenticationContext in
guard let self = self else { return }
diff --git a/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+Diffable.swift b/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+Diffable.swift
index b8ac3e26..6e87fddb 100644
--- a/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+Diffable.swift
@@ -10,6 +10,7 @@ import os.log
import UIKit
import CoreData
import CoreDataStack
+import AppShared
extension SearchTimelineViewModel {
@MainActor func setupDiffableDataSource(
@@ -18,7 +19,12 @@ extension SearchTimelineViewModel {
) {
let configuration = StatusSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- timelineMiddleLoaderTableViewCellDelegate: nil
+ timelineMiddleLoaderTableViewCellDelegate: nil,
+ statusViewConfigurationContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
diff --git a/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+State.swift b/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+State.swift
index 8ac69c0c..5fee8d3e 100644
--- a/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+State.swift
+++ b/TwidereX/Scene/Search/SearchResult/Status/SearchTimelineViewModel+State.swift
@@ -72,7 +72,7 @@ extension SearchTimelineViewModel.State {
}
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Search/SearchResult/User/SearchUserViewModel+State.swift b/TwidereX/Scene/Search/SearchResult/User/SearchUserViewModel+State.swift
index 297bdff9..6c3de0b0 100644
--- a/TwidereX/Scene/Search/SearchResult/User/SearchUserViewModel+State.swift
+++ b/TwidereX/Scene/Search/SearchResult/User/SearchUserViewModel+State.swift
@@ -72,7 +72,7 @@ extension SearchUserViewModel.State {
}
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext
else {
stateMachine.enter(Fail.self)
return
diff --git a/TwidereX/Scene/Search/Trend/TrendViewModel.swift b/TwidereX/Scene/Search/Trend/TrendViewModel.swift
index b1dfe68a..1c9b4ce1 100644
--- a/TwidereX/Scene/Search/Trend/TrendViewModel.swift
+++ b/TwidereX/Scene/Search/Trend/TrendViewModel.swift
@@ -31,7 +31,7 @@ final class TrendViewModel {
self.trendService = TrendService(apiService: context.apiService)
// end init
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] authenticationContext in
diff --git a/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem+ViewModel.swift b/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem+ViewModel.swift
index cdc58ca5..b45749cd 100644
--- a/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem+ViewModel.swift
+++ b/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem+ViewModel.swift
@@ -10,42 +10,93 @@ import UIKit
import Combine
import TwidereCore
import CoreDataStack
+import TwidereUI
extension AvatarBarButtonItem {
public class ViewModel: ObservableObject {
- var bindDisposeBag = Set()
+ var disposeBag = Set()
var observations = Set()
+ @Published var name: String?
+ @Published var username: String?
@Published var avatarURL: URL?
+
+ @Published var avatarStyle: UserDefaults.AvatarStyle = UserDefaults.shared.avatarStyle
+
+ init() {
+ UserDefaults.shared
+ .observe(\.avatarStyle, options: [.initial, .new]) { defaults, _ in
+ self.avatarStyle = defaults.avatarStyle
+ }
+ .store(in: &observations)
+ }
}
}
extension AvatarBarButtonItem.ViewModel {
func bind(view: AvatarBarButtonItem) {
- // avatar
+ bindAvatar(view: view)
+ bindAccessibility(view: view)
+ }
+
+ private func bindAvatar(view: AvatarBarButtonItem) {
$avatarURL
.sink { avatarURL in
let configuration = AvatarImageView.Configuration(url: avatarURL)
view.avatarButton.avatarImageView.configure(configuration: configuration)
}
- .store(in: &bindDisposeBag)
- UserDefaults.shared
- .observe(\.avatarStyle, options: [.initial, .new]) { defaults, _ in
- let avatarStyle = defaults.avatarStyle
+ .store(in: &disposeBag)
+
+ func cornerConfiguration(avatarStyle: UserDefaults.AvatarStyle) -> AvatarImageView.CornerConfiguration {
+ switch avatarStyle {
+ case .circle:
+ return .init(corner: .circle)
+ case .roundedSquare:
+ return .init(corner: .scale(ratio: 4))
+ }
+ }
+
+ view.avatarButton.avatarImageView.configure(
+ cornerConfiguration: cornerConfiguration(avatarStyle: avatarStyle)
+ )
+
+ $avatarStyle
+ .removeDuplicates()
+ .sink { avatarStyle in
+ let cornerConfiguration = cornerConfiguration(avatarStyle: avatarStyle)
+
let animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: UISpringTimingParameters())
animator.addAnimations { [weak view] in
guard let view = view else { return }
- switch avatarStyle {
- case .circle:
- view.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .circle))
- case .roundedSquare:
- view.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .scale(ratio: 4)))
- }
+ view.avatarButton.avatarImageView.configure(cornerConfiguration: cornerConfiguration)
}
animator.startAnimation()
}
- .store(in: &observations)
+ .store(in: &disposeBag)
+ }
+
+ private func bindAccessibility(view: AvatarBarButtonItem) {
+ Publishers.CombineLatest(
+ $name,
+ $username
+ )
+ .sink { name, username in
+ let info = [name, username]
+ .compactMap { $0 }
+ .joined(separator: ", ")
+
+ guard !info.isEmpty else {
+ view.avatarButton.accessibilityLabel = nil
+ return
+ }
+
+ view.accessibilityLabel = L10n.Accessibility.Scene.ManageAccounts.currentSignInUser(info)
+ }
+ .store(in: &disposeBag)
+
+ view.accessibilityHint = L10n.Accessibility.VoiceOver.doubleTapAndHoldToOpenTheAccountsPanel
}
+
}
extension AvatarBarButtonItem {
@@ -76,6 +127,16 @@ extension AvatarBarButtonItem {
return
}
+ user.publisher(for: \.name)
+ .map { $0 as String? }
+ .assign(to: \.name, on: viewModel)
+ .store(in: &disposeBag)
+
+ user.publisher(for: \.username)
+ .map { $0 as String? }
+ .assign(to: \.username, on: viewModel)
+ .store(in: &disposeBag)
+
// avatar
user.publisher(for: \.profileImageURL)
.map { _ in user.avatarImageURL() }
@@ -88,6 +149,16 @@ extension AvatarBarButtonItem {
return
}
+ user.publisher(for: \.displayName)
+ .map { _ in user.displayName as String? }
+ .assign(to: \.name, on: viewModel)
+ .store(in: &disposeBag)
+
+ user.publisher(for: \.acct)
+ .map { _ in user.acctWithDomain as String? }
+ .assign(to: \.username, on: viewModel)
+ .store(in: &disposeBag)
+
// avatar
user.publisher(for: \.avatar)
.map { avatar in avatar.flatMap { URL(string: $0) } }
diff --git a/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem.swift b/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem.swift
index d504c436..3f99ee08 100644
--- a/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem.swift
+++ b/TwidereX/Scene/Share/View/Button/AvatarBarButtonItem.swift
@@ -6,15 +6,23 @@
// Copyright © 2020 Twidere. All rights reserved.
//
+import os.log
import UIKit
import Combine
import TwidereUI
+public protocol AvatarBarButtonItemDelegate: AnyObject {
+ func avatarBarButtonItem(_ barButtonItem: AvatarBarButtonItem, didLongPressed sender: UILongPressGestureRecognizer)
+}
+
public final class AvatarBarButtonItem: UIBarButtonItem {
+ let logger = Logger(subsystem: "AvatarBarButtonItem", category: "View")
+
var disposeBag = Set()
+ weak var delegate: AvatarBarButtonItemDelegate?
- public static let avatarButtonSize = CGSize(width: 30, height: 30)
+ public static let size = CGSize(width: 30, height: 30)
private(set) lazy var viewModel: ViewModel = {
let viewModel = ViewModel()
@@ -26,8 +34,8 @@ public final class AvatarBarButtonItem: UIBarButtonItem {
let button = AvatarButton()
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
- button.widthAnchor.constraint(equalToConstant: avatarButtonSize.width).priority(.required - 1),
- button.heightAnchor.constraint(equalToConstant: avatarButtonSize.height).priority(.required - 1),
+ button.widthAnchor.constraint(equalToConstant: size.width).priority(.required - 1),
+ button.heightAnchor.constraint(equalToConstant: size.height).priority(.required - 1),
])
return button
}()
@@ -48,6 +56,25 @@ extension AvatarBarButtonItem {
private func _init() {
customView = avatarButton
+
+ let avatarButtonLongPressGestureRecognizer = UILongPressGestureRecognizer()
+ avatarButtonLongPressGestureRecognizer.addTarget(self, action: #selector(AvatarBarButtonItem.avatarButtonDidLongPressed(_:)))
+ avatarButton.addGestureRecognizer(avatarButtonLongPressGestureRecognizer)
+ }
+
+}
+
+extension AvatarBarButtonItem {
+
+ @objc func avatarButtonDidLongPressed(_ sender: UILongPressGestureRecognizer) {
+ logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
+
+ switch sender.state {
+ case .began:
+ delegate?.avatarBarButtonItem(self, didLongPressed: sender)
+ default:
+ break
+ }
}
}
diff --git a/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell+ViewModel.swift b/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell+ViewModel.swift
index 0bf10e76..fda8b17e 100644
--- a/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell+ViewModel.swift
+++ b/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell+ViewModel.swift
@@ -11,8 +11,10 @@ import Combine
import SwiftUI
import CoreDataStack
import AppShared
+import TwidereUI
extension StatusTableViewCell {
+
final class ViewModel {
enum Value {
case feed(Feed)
@@ -22,20 +24,16 @@ extension StatusTableViewCell {
}
let value: Value
- let activeAuthenticationContext: AnyPublisher
- init(
- value: Value,
- activeAuthenticationContext: AnyPublisher
- ) {
+ init(value: Value) {
self.value = value
- self.activeAuthenticationContext = activeAuthenticationContext
}
}
func configure(
tableView: UITableView,
viewModel: ViewModel,
+ configurationContext: StatusView.ConfigurationContext,
delegate: StatusViewTableViewCellDelegate?
) {
if statusView.frame == .zero {
@@ -50,12 +48,6 @@ extension StatusTableViewCell {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): did layout for new cell")
}
- let configurationContext = StatusView.ConfigurationContext(
- dateTimeProvider: DateTimeSwiftProvider(),
- twitterTextProvider: OfficialTwitterTextProvider(),
- activeAuthenticationContext: viewModel.activeAuthenticationContext
- )
-
switch viewModel.value {
case .feed(let feed):
statusView.configure(
@@ -84,19 +76,21 @@ extension StatusTableViewCell {
configureSeparator(style: .inset)
}
+ self.delegate = delegate
- statusView.viewModel.contentRevealChangePublisher
+ statusView.viewModel.$isContentReveal
+ .removeDuplicates()
+ .dropFirst()
.receive(on: DispatchQueue.main)
- .sink { [weak tableView] _ in
+ .sink { [weak tableView, weak self] _ in
guard let tableView = tableView else { return }
+ guard let _ = self else { return }
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
.store(in: &disposeBag)
-
- self.delegate = delegate
}
}
diff --git a/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell.swift b/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell.swift
index af2f38b8..2a1b6a4d 100644
--- a/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell.swift
+++ b/TwidereX/Scene/Share/View/TableViewCell/StatusTableViewCell.swift
@@ -9,13 +9,14 @@
import os.log
import UIKit
import Combine
+import TwidereUI
class StatusTableViewCell: UITableViewCell {
private var _disposeBag = Set()
var disposeBag = Set()
- let logger = Logger(subsystem: "StatusTableViewCell", category: "UI")
+ let logger = Logger(subsystem: "StatusTableViewCell", category: "View")
weak var delegate: StatusViewTableViewCellDelegate?
@@ -83,7 +84,6 @@ extension StatusTableViewCell {
// a11y
isAccessibilityElement = true
- accessibilityElements = [statusView]
statusView.viewModel.$groupedAccessibilityLabel
.receive(on: DispatchQueue.main)
.sink { [weak self] accessibilityLabel in
diff --git a/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell+ViewModel.swift b/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell+ViewModel.swift
index ba10bb68..04e879f4 100644
--- a/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell+ViewModel.swift
+++ b/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell+ViewModel.swift
@@ -12,8 +12,10 @@ import SwiftUI
import CoreDataStack
import TwidereCore
import AppShared
+import TwidereUI
extension StatusThreadRootTableViewCell {
+
final class ViewModel {
enum Value {
case statusObject(StatusObject)
@@ -22,20 +24,18 @@ extension StatusThreadRootTableViewCell {
}
let value: Value
- let activeAuthenticationContext: AnyPublisher
init(
- value: Value,
- activeAuthenticationContext: AnyPublisher
+ value: Value
) {
self.value = value
- self.activeAuthenticationContext = activeAuthenticationContext
}
}
func configure(
tableView: UITableView,
viewModel: StatusThreadRootTableViewCell.ViewModel,
+ configurationContext: StatusView.ConfigurationContext,
delegate: StatusViewTableViewCellDelegate?
) {
if statusView.frame == .zero {
@@ -48,13 +48,7 @@ extension StatusThreadRootTableViewCell {
statusView.quoteStatusView?.contentTextView.preferredMaxLayoutWidth = statusView.quoteStatusView?.contentMaxLayoutWidth
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): did layout for new cell")
}
-
- let configurationContext = StatusView.ConfigurationContext(
- dateTimeProvider: DateTimeSwiftProvider(),
- twitterTextProvider: OfficialTwitterTextProvider(),
- activeAuthenticationContext: viewModel.activeAuthenticationContext
- )
-
+
switch viewModel.value {
case .statusObject(let object):
statusView.configure(
@@ -74,18 +68,21 @@ extension StatusThreadRootTableViewCell {
)
}
-
- statusView.viewModel.contentRevealChangePublisher
+ self.delegate = delegate
+
+ statusView.viewModel.$isContentReveal
+ .removeDuplicates()
+ .dropFirst()
.receive(on: DispatchQueue.main)
- .sink { [weak tableView] _ in
+ .sink { [weak tableView, weak self] _ in
guard let tableView = tableView else { return }
+ guard let _ = self else { return }
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
.store(in: &disposeBag)
-
- self.delegate = delegate
}
+
}
diff --git a/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell.swift b/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell.swift
index 11922a18..47c63bfe 100644
--- a/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell.swift
+++ b/TwidereX/Scene/Share/View/TableViewCell/StatusThreadRootTableViewCell.swift
@@ -9,6 +9,7 @@
import os.log
import UIKit
import Combine
+import TwidereUI
final class StatusThreadRootTableViewCell: UITableViewCell {
@@ -91,21 +92,34 @@ extension StatusThreadRootTableViewCell {
isAccessibilityElement = false
}
+}
+
+extension StatusThreadRootTableViewCell {
+
override var accessibilityElements: [Any]? {
get {
- return [
+ let elements: [UIView?] = [
statusView.headerTextLabel,
statusView.authorAvatarButton,
statusView.authorNameLabel,
+ statusView.authorUsernameLabel,
+ statusView.visibilityImageView,
statusView.spoilerContentTextView,
+ statusView.expandContentButton,
statusView.contentTextView,
statusView.mediaGridContainerView,
+ statusView.pollTableView,
+ statusView.pollVoteDescriptionLabel,
+ statusView.pollVoteButton,
statusView.quoteStatusView,
+ statusView.locationLabel,
statusView.metricsDashboardView,
statusView.toolbar,
]
- .compactMap { $0 }
- .filter { !$0.isHidden }
+
+ return elements
+ .compactMap { $0 }
+ .filter { !$0.isHidden }
}
set { }
}
diff --git a/TwidereX/Scene/Share/View/TableViewCell/StatusViewTableViewCellDelegate.swift b/TwidereX/Scene/Share/View/TableViewCell/StatusViewTableViewCellDelegate.swift
index 15e85886..af108290 100644
--- a/TwidereX/Scene/Share/View/TableViewCell/StatusViewTableViewCellDelegate.swift
+++ b/TwidereX/Scene/Share/View/TableViewCell/StatusViewTableViewCellDelegate.swift
@@ -23,7 +23,7 @@ protocol StatusViewContainerTableViewCell: UITableViewCell, AutoGenerateProtocol
// sourcery: protocolName = "StatusViewDelegate"
// sourcery: replaceOf = "statusView(_"
// sourcery: replaceWith = "func tableViewCell(_ cell: UITableViewCell,"
-protocol StatusViewTableViewCellDelegate: AnyObject, AutoGenerateProtocolDelegate {
+protocol StatusViewTableViewCellDelegate: AutoGenerateProtocolDelegate {
// sourcery:inline:StatusViewTableViewCellDelegate.AutoGenerateProtocolDelegate
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, headerDidPressed header: UIView)
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton)
diff --git a/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewController.swift b/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewController.swift
index 537ba08e..8d4f0088 100644
--- a/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewController.swift
+++ b/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewController.swift
@@ -102,7 +102,7 @@ extension DrawerSidebarViewController {
settingCollectionView: settingCollectionView
)
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
let user = authenticationContext?.user(in: self.context.managedObjectContext)
diff --git a/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewModel+Diffable.swift b/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewModel+Diffable.swift
index 6fc94fcb..3d21c018 100644
--- a/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewModel+Diffable.swift
+++ b/TwidereX/Scene/Sidebar/Drawer/DrawerSidebarViewModel+Diffable.swift
@@ -20,7 +20,7 @@ extension DrawerSidebarViewModel {
sidebarSnapshot.appendSections([.main])
sidebarDiffableDataSource?.applySnapshotUsingReloadData(sidebarSnapshot)
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
var snapshot = NSDiffableDataSourceSnapshot()
diff --git a/TwidereX/Scene/Sidebar/Drawer/View/DrawerSidebarHeaderView.swift b/TwidereX/Scene/Sidebar/Drawer/View/DrawerSidebarHeaderView.swift
index 5dc2af07..5d5b0f51 100644
--- a/TwidereX/Scene/Sidebar/Drawer/View/DrawerSidebarHeaderView.swift
+++ b/TwidereX/Scene/Sidebar/Drawer/View/DrawerSidebarHeaderView.swift
@@ -39,7 +39,7 @@ final class DrawerSidebarHeaderView: UIView {
let avatarView: ProfileAvatarView = {
let avatarView = ProfileAvatarView()
- avatarView.dimension = DrawerSidebarHeaderView.avatarViewSize.width
+ avatarView.setup(dimension: .inline)
return avatarView
}()
@@ -120,8 +120,8 @@ extension DrawerSidebarHeaderView {
avatarView.translatesAutoresizingMaskIntoConstraints = false
infoStackView.addArrangedSubview(avatarView)
NSLayoutConstraint.activate([
- avatarView.widthAnchor.constraint(equalToConstant: 40).priority(.required - 1),
- avatarView.heightAnchor.constraint(equalToConstant: 40).priority(.required - 1),
+ avatarView.widthAnchor.constraint(equalToConstant: DrawerSidebarHeaderView.avatarViewSize.width).priority(.required - 1),
+ avatarView.heightAnchor.constraint(equalToConstant: DrawerSidebarHeaderView.avatarViewSize.height).priority(.required - 1),
])
let nameStackView = UIStackView()
diff --git a/TwidereX/Scene/StatusThread/StatusThreadViewModel+Diffable.swift b/TwidereX/Scene/StatusThread/StatusThreadViewModel+Diffable.swift
index b79f0091..abafba71 100644
--- a/TwidereX/Scene/StatusThread/StatusThreadViewModel+Diffable.swift
+++ b/TwidereX/Scene/StatusThread/StatusThreadViewModel+Diffable.swift
@@ -11,6 +11,7 @@ import UIKit
import Combine
import CoreData
import CoreDataStack
+import AppShared
extension StatusThreadViewModel {
@@ -20,8 +21,14 @@ extension StatusThreadViewModel {
) {
let configuration = StatusSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- timelineMiddleLoaderTableViewCellDelegate: nil
+ timelineMiddleLoaderTableViewCellDelegate: nil,
+ statusViewConfigurationContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
+
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
diff --git a/TwidereX/Scene/StatusThread/StatusThreadViewModel+LoadThreadState.swift b/TwidereX/Scene/StatusThread/StatusThreadViewModel+LoadThreadState.swift
index 37a8064e..726534b8 100644
--- a/TwidereX/Scene/StatusThread/StatusThreadViewModel+LoadThreadState.swift
+++ b/TwidereX/Scene/StatusThread/StatusThreadViewModel+LoadThreadState.swift
@@ -96,7 +96,7 @@ extension StatusThreadViewModel.LoadThreadState {
if twitterConversation.conversationID == nil {
// fetch conversationID if not exist
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value?.twitterAuthenticationContext else {
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext?.twitterAuthenticationContext else {
await enter(state: PrepareFail.self)
return
}
@@ -107,7 +107,7 @@ extension StatusThreadViewModel.LoadThreadState {
authenticationContext: authenticationContext
)
guard let conversationID = response.value.data?.first?.conversationID else {
- assertionFailure()
+ // assertionFailure()
await enter(state: PrepareFail.self)
return
}
@@ -247,7 +247,7 @@ extension StatusThreadViewModel.LoadThreadState {
twitterConversation: StatusThreadViewModel.ThreadContext.TwitterConversation
) async -> [TwitterStatusThreadLeafViewModel.Node] {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return [] }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value?.twitterAuthenticationContext,
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext?.twitterAuthenticationContext,
let conversationID = twitterConversation.conversationID
else {
await enter(state: Fail.self)
@@ -336,7 +336,7 @@ extension StatusThreadViewModel.LoadThreadState {
descendantNodes: []
)
}
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value?.mastodonAuthenticationContext
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext?.mastodonAuthenticationContext
else {
await enter(state: Fail.self)
return MastodonContextResponse(
diff --git a/TwidereX/Scene/StatusThread/Twitter/TwitterStatusThreadReplyViewModel+State.swift b/TwidereX/Scene/StatusThread/Twitter/TwitterStatusThreadReplyViewModel+State.swift
index 610de87c..c8b98111 100644
--- a/TwidereX/Scene/StatusThread/Twitter/TwitterStatusThreadReplyViewModel+State.swift
+++ b/TwidereX/Scene/StatusThread/Twitter/TwitterStatusThreadReplyViewModel+State.swift
@@ -220,7 +220,7 @@ extension TwitterStatusThreadReplyViewModel.State {
super.didEnter(from: previousState)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value,
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext,
case let .twitter(twitterAuthenticationContext) = authenticationContext
else {
return
diff --git a/TwidereX/Scene/Timeline/Base/TimelineViewController.swift b/TwidereX/Scene/Timeline/Base/TimelineViewController.swift
index 83b59f71..cf369b50 100644
--- a/TwidereX/Scene/Timeline/Base/TimelineViewController.swift
+++ b/TwidereX/Scene/Timeline/Base/TimelineViewController.swift
@@ -98,10 +98,11 @@ extension TimelineViewController {
self.avatarBarButtonItem.avatarButton.addTarget(self, action: #selector(TimelineViewController.avatarButtonPressed(_:)), for: .touchUpInside)
}
.store(in: &disposeBag)
+ avatarBarButtonItem.delegate = self
// bind avatarBarButtonItem data
Publishers.CombineLatest(
- context.authenticationService.activeAuthenticationContext,
+ context.authenticationService.$activeAuthenticationContext,
viewModel.viewDidAppear.eraseToAnyPublisher()
)
.receive(on: DispatchQueue.main)
@@ -259,8 +260,11 @@ extension TimelineViewController {
apiService: context.apiService,
authenticationService: context.authenticationService,
mastodonEmojiService: context.mastodonEmojiService,
- dateTimeProvider: DateTimeSwiftProvider(),
- twitterTextProvider: OfficialTwitterTextProvider()
+ statusViewConfigureContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
)
coordinator.present(scene: .compose(viewModel: composeViewModel, contentViewModel: composeContentViewModel), from: self, transition: .modal(animated: true, completion: nil))
@@ -295,36 +299,11 @@ extension TimelineViewController: UITableViewDelegate, AutoGenerateTableViewDele
}
// sourcery:end
-
-// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
-// guard let diffableDataSource = viewModel.diffableDataSource else { return 100 }
-// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return 100 }
-//
-// guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else {
-// return 200
-// }
-// // os_log("%{public}s[%{public}ld], %{public}s: cache cell frame %s", ((#file as NSString).lastPathComponent), #line, #function, frame.debugDescription)
-//
-// return ceil(frame.height)
-// }
-//
-// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
-// handleTableView(tableView, willDisplay: cell, forRowAt: indexPath)
-// }
-//
-// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
-// handleTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
-//
-// guard let diffableDataSource = viewModel.diffableDataSource else { return }
-// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
-//
-// let key = item.hashValue
-// let frame = cell.frame
-// viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key))
-// }
-
}
+// MARK: - AvatarBarButtonItemDelegate
+extension TimelineViewController: AvatarBarButtonItemDelegate { }
+
// MARK: - ScrollViewContainer
extension TimelineViewController: ScrollViewContainer {
diff --git a/TwidereX/Scene/Timeline/Base/TimelineViewModel+Diffable.swift b/TwidereX/Scene/Timeline/Base/TimelineViewModel+Diffable.swift
index 9a60100d..d38c7974 100644
--- a/TwidereX/Scene/Timeline/Base/TimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/Timeline/Base/TimelineViewModel+Diffable.swift
@@ -77,7 +77,7 @@ extension TimelineViewModel {
defer {
isLoadingLatest = false
}
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
do {
switch authenticationContext {
case .twitter(let authenticationContext):
@@ -111,7 +111,7 @@ extension TimelineViewModel {
// load timeline gap
func loadMore(item: StatusItem) async {
guard case let .feedLoader(record) = item else { return }
- guard let authenticationContext = context.authenticationService.activeAuthenticationContext.value else { return }
+ guard let authenticationContext = context.authenticationService.activeAuthenticationContext else { return }
guard let diffableDataSource = diffableDataSource else { return }
var snapshot = diffableDataSource.snapshot()
diff --git a/TwidereX/Scene/Timeline/Base/TimelineViewModel+LoadOldestState.swift b/TwidereX/Scene/Timeline/Base/TimelineViewModel+LoadOldestState.swift
index 5bdd7fde..8f8955e6 100644
--- a/TwidereX/Scene/Timeline/Base/TimelineViewModel+LoadOldestState.swift
+++ b/TwidereX/Scene/Timeline/Base/TimelineViewModel+LoadOldestState.swift
@@ -90,7 +90,7 @@ extension TimelineViewModel.LoadOldestState {
private func fetch(anchor record: StatusRecord) async {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
- guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext.value else {
+ guard let authenticationContext = viewModel.context.authenticationService.activeAuthenticationContext else {
await enter(state: Fail.self)
return
}
diff --git a/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel+Diffable.swift b/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel+Diffable.swift
index beffdd75..5a9017e8 100644
--- a/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel+Diffable.swift
@@ -11,6 +11,8 @@ import UIKit
import Combine
import CoreData
import CoreDataStack
+import TwidereCore
+import AppShared
extension FederatedTimelineViewModel {
@@ -21,7 +23,12 @@ extension FederatedTimelineViewModel {
) {
let configuration = StatusSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate
+ timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,
+ statusViewConfigurationContext: StatusView.ConfigurationContext(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
diff --git a/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel.swift b/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel.swift
index e6e976b6..e035c63a 100644
--- a/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel.swift
+++ b/TwidereX/Scene/Timeline/Federated/FederatedTimelineViewModel.swift
@@ -21,7 +21,7 @@ final class FederatedTimelineViewModel: TimelineViewModel {
kind: .federated(local: local)
)
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
guard let authenticationContext = authenticationContext else {
diff --git a/TwidereX/Scene/Timeline/Home/HomeTimelineViewController+DebugAction.swift b/TwidereX/Scene/Timeline/Home/HomeTimelineViewController+DebugAction.swift
index 57b46f0c..637e578e 100644
--- a/TwidereX/Scene/Timeline/Home/HomeTimelineViewController+DebugAction.swift
+++ b/TwidereX/Scene/Timeline/Home/HomeTimelineViewController+DebugAction.swift
@@ -250,7 +250,7 @@ extension HomeTimelineViewController {
case blockingAuthor
func match(item: StatusItem) -> Bool {
- let authenticationContext = AppContext.shared.authenticationService.activeAuthenticationContext.value
+ let authenticationContext = AppContext.shared.authenticationService.activeAuthenticationContext
switch item {
case .feed(let record):
guard let feed = record.object(in: AppContext.shared.managedObjectContext) else { return false }
diff --git a/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel+Diffable.swift b/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel+Diffable.swift
index ceae35a3..13885a48 100644
--- a/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel+Diffable.swift
+++ b/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel+Diffable.swift
@@ -11,6 +11,7 @@ import UIKit
import Combine
import CoreData
import CoreDataStack
+import AppShared
extension HomeTimelineViewModel {
@@ -21,7 +22,12 @@ extension HomeTimelineViewModel {
) {
let configuration = StatusSection.Configuration(
statusViewTableViewCellDelegate: statusViewTableViewCellDelegate,
- timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate
+ timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,
+ statusViewConfigurationContext: .init(
+ dateTimeProvider: DateTimeSwiftProvider(),
+ twitterTextProvider: OfficialTwitterTextProvider(),
+ authenticationContext: context.authenticationService.$activeAuthenticationContext
+ )
)
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
diff --git a/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel.swift b/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel.swift
index 9ed513b6..447b0244 100644
--- a/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel.swift
+++ b/TwidereX/Scene/Timeline/Home/HomeTimelineViewModel.swift
@@ -14,7 +14,7 @@ final class HomeTimelineViewModel: TimelineViewModel {
init(context: AppContext) {
super.init(context: context, kind: .home)
- context.authenticationService.activeAuthenticationContext
+ context.authenticationService.$activeAuthenticationContext
.sink { [weak self] authenticationContext in
guard let self = self else { return }
let emptyFeedPredicate = Feed.nonePredicate()
diff --git a/TwidereX/Template/AutoGenerateProtocolDelegate.swift b/TwidereX/Template/AutoGenerateProtocolDelegate.swift
index d38ee646..1dab93d5 100644
--- a/TwidereX/Template/AutoGenerateProtocolDelegate.swift
+++ b/TwidereX/Template/AutoGenerateProtocolDelegate.swift
@@ -7,4 +7,4 @@
import Foundation
-@objc protocol AutoGenerateProtocolDelegate { }
+@objc protocol AutoGenerateProtocolDelegate: AnyObject { }
diff --git a/TwidereXTests/Info.plist b/TwidereXTests/Info.plist
index 37ac53d2..24413898 100644
--- a/TwidereXTests/Info.plist
+++ b/TwidereXTests/Info.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.4
+ 1.2.6
CFBundleVersion
- 74
+ 77
diff --git a/TwidereXUITests/Info.plist b/TwidereXUITests/Info.plist
index 37ac53d2..24413898 100644
--- a/TwidereXUITests/Info.plist
+++ b/TwidereXUITests/Info.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.4
+ 1.2.6
CFBundleVersion
- 74
+ 77