From 8f7b88f07f6eb189dce0efed81cd47b304e71d47 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 May 2023 15:39:53 +0800 Subject: [PATCH 1/7] fix: potential crash issue when get object from objectID --- .../CoreDataStack/Utility/ManagedObjectRecord.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/TwidereSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift b/TwidereSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift index de4915be..63a361be 100644 --- a/TwidereSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift +++ b/TwidereSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift @@ -18,7 +18,12 @@ public class ManagedObjectRecord: Hashable { } public func object(in managedObjectContext: NSManagedObjectContext) -> T? { - return managedObjectContext.object(with: objectID) as? T + do { + return try managedObjectContext.existingObject(with: objectID) as? T + } catch { + assertionFailure(error.localizedDescription) + return nil + } } public static func == (lhs: ManagedObjectRecord, rhs: ManagedObjectRecord) -> Bool { From 0b3a115a3da29f2936e7cf69430141270a2df1f6 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 May 2023 18:53:46 +0800 Subject: [PATCH 2/7] fix: add missing lock icon for protected enabled account --- .../TwidereCore/Model/User/UserObject.swift | 9 ++ .../Content/StatusView+ViewModel.swift | 10 +- .../TwidereUI/Content/StatusView.swift | 28 +++-- .../Content/UserView+ViewModel.swift | 11 +- .../Sources/TwidereUI/Content/UserView.swift | 115 +++++++++++------- 5 files changed, 115 insertions(+), 58 deletions(-) diff --git a/TwidereSDK/Sources/TwidereCore/Model/User/UserObject.swift b/TwidereSDK/Sources/TwidereCore/Model/User/UserObject.swift index ff69aa4d..2a1e0705 100644 --- a/TwidereSDK/Sources/TwidereCore/Model/User/UserObject.swift +++ b/TwidereSDK/Sources/TwidereCore/Model/User/UserObject.swift @@ -99,6 +99,15 @@ extension UserObject { return object.avatar.flatMap { URL(string: $0) } } } + + public var protected: Bool { + switch self { + case .twitter(let object): + return object.protected + case .mastodon(let object): + return object.locked + } + } } extension UserObject { diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift index db740060..a338aa46 100644 --- a/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift +++ b/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift @@ -56,7 +56,9 @@ extension StatusView { @Published public var authorName: MetaContent = PlaintextMetaContent(string: "") @Published public var authorUsernme = "" @Published public var authorUserIdentifier: UserIdentifier? - + + @Published public var protected: Bool = false + // static let pollOptionOrdinalNumberFormatter: NumberFormatter = { // let formatter = NumberFormatter() // formatter.numberStyle = .ordinal @@ -68,7 +70,7 @@ extension StatusView { // @Published public var authorAvatarImageURL: URL? // @Published public var authorUsername: String? // -// @Published public var protected: Bool = false + // content @Published public var spoilerContent: MetaContent? @@ -1105,6 +1107,8 @@ extension StatusView.ViewModel { .assign(to: &$authorName) status.author.publisher(for: \.username) .assign(to: &$authorUsernme) + status.author.publisher(for: \.protected) + .assign(to: &$protected) authorUserIdentifier = .twitter(.init(id: status.author.id)) // timestamp @@ -1264,6 +1268,8 @@ extension StatusView.ViewModel { status.author.publisher(for: \.username) .map { _ in status.author.acct } .assign(to: &$authorUsernme) + status.author.publisher(for: \.locked) + .assign(to: &$protected) authorUserIdentifier = .mastodon(.init(domain: status.author.domain, id: status.author.id)) // visibility diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift index b638837c..ed1a2cf1 100644 --- a/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift +++ b/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift @@ -68,6 +68,7 @@ public struct StatusView: View { @Environment(\.dynamicTypeSize) var dynamicTypeSize @ScaledMetric(relativeTo: .subheadline) private var visibilityIconImageDimension: CGFloat = 16 @ScaledMetric(relativeTo: .headline) private var inlineAvatarButtonDimension: CGFloat = 20 + @ScaledMetric(relativeTo: .headline) private var lockImageDimension: CGFloat = 16 public init(viewModel: StatusView.ViewModel) { self.viewModel = viewModel @@ -313,16 +314,25 @@ extension StatusView { } }() nameLayout { - // name - LabelRepresentable( - metaContent: viewModel.authorName, - textStyle: .statusAuthorName, - setupLabel: { label in - label.setContentHuggingPriority(.defaultHigh, for: .horizontal) - label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + HStack { + // name + LabelRepresentable( + metaContent: viewModel.authorName, + textStyle: .statusAuthorName, + setupLabel: { label in + label.setContentHuggingPriority(.defaultHigh, for: .horizontal) + label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + } + ) + .fixedSize(horizontal: false, vertical: true) + // lock + if viewModel.protected { + Image(uiImage: Asset.ObjectTools.lockMini.image.withRenderingMode(.alwaysTemplate)) + .resizable() + .frame(width: lockImageDimension, height: lockImageDimension) + .foregroundColor(.secondary) } - ) - .fixedSize(horizontal: false, vertical: true) + } .layoutPriority(0.618) // username LabelRepresentable( diff --git a/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift index 9307a5d7..de83f88f 100644 --- a/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift +++ b/TwidereSDK/Sources/TwidereUI/Content/UserView+ViewModel.swift @@ -43,6 +43,7 @@ extension UserView { @Published public var platform: Platform = .none @Published public var isMyself: Bool = false + @Published public var protected: Bool = false // @Published public var authenticationContext: AuthenticationContext? // me // @Published public var userAuthenticationContext: AuthenticationContext? @@ -55,10 +56,9 @@ extension UserView { // @Published public var name: MetaContent? = PlaintextMetaContent(string: " ") // @Published public var username: String? // -// @Published public var protected: Bool = false -// + // @Published public var followerCount: Int? -// + // follow request @Published public var isFollowRequestActionDisplay = false @Published public var isFollowRequestBusy = false @@ -421,6 +421,8 @@ extension UserView.ViewModel { .assign(to: &$name) user.publisher(for: \.username) .assign(to: &$username) + user.publisher(for: \.protected) + .assign(to: &$protected) } public convenience init( @@ -448,6 +450,8 @@ extension UserView.ViewModel { user.publisher(for: \.username) .map { _ in user.acctWithDomain } .assign(to: &$username) + user.publisher(for: \.locked) + .assign(to: &$protected) } } @@ -467,6 +471,7 @@ extension UserView.ViewModel { username = "username" platform = .twitter notificationBadgeCount = 10 + protected = true } } #endif diff --git a/TwidereSDK/Sources/TwidereUI/Content/UserView.swift b/TwidereSDK/Sources/TwidereUI/Content/UserView.swift index 1c703746..e4022696 100644 --- a/TwidereSDK/Sources/TwidereUI/Content/UserView.swift +++ b/TwidereSDK/Sources/TwidereUI/Content/UserView.swift @@ -26,38 +26,74 @@ public struct UserView: View { @ObservedObject public private(set) var viewModel: ViewModel + @Environment(\.dynamicTypeSize) var dynamicTypeSize + @ScaledMetric(relativeTo: .headline) private var lockImageDimension: CGFloat = 16 + public init(viewModel: UserView.ViewModel) { self.viewModel = viewModel } public var body: some View { - HStack(alignment: .center, spacing: .zero) { - // avatar - avatarButton - .padding(.trailing, StatusView.hangingAvatarButtonTrailingSpacing) - // info - VStackLayout(alignment: .leading, spacing: .zero) { - headlineView - subheadlineView - } - .frame(alignment: .leading) - Spacer() - // accessory view - accessoryView - } // end HStack - .padding(.vertical, viewModel.verticalMargin) - .overlay { - if viewModel.isSeparateLineDisplay { - HStack(spacing: .zero) { - Color.clear.frame(width: StatusView.hangingAvatarButtonDimension + StatusView.hangingAvatarButtonTrailingSpacing) - VStack(spacing: .zero) { + Group { + if dynamicTypeSize < .accessibility1 { + HStack(alignment: .center, spacing: .zero) { + // avatar + avatarButton + .padding(.trailing, StatusView.hangingAvatarButtonTrailingSpacing) + // info + VStackLayout(alignment: .leading, spacing: .zero) { + headlineView + subheadlineView + } + .frame(alignment: .leading) + Spacer() + // accessory view + accessoryView + } // end HStack + .padding(.vertical, viewModel.verticalMargin) + .overlay { + if viewModel.isSeparateLineDisplay { + HStack(spacing: .zero) { + Color.clear.frame(width: StatusView.hangingAvatarButtonDimension + StatusView.hangingAvatarButtonTrailingSpacing) + VStack(spacing: .zero) { + Spacer() + Divider() + Color.clear.frame(height: 1) + } + } // end HStack + } // end if + } // end .overlay + } else { + VStack(spacing: .zero) { + HStack { + // avatar + avatarButton + .padding(.trailing, StatusView.hangingAvatarButtonTrailingSpacing) Spacer() - Divider() - Color.clear.frame(height: 1) + // accessory view + accessoryView + } + // info + VStackLayout(alignment: .leading, spacing: .zero) { + headlineView + subheadlineView } + .frame(alignment: .leading) } // end HStack - } // end if - } // end .overlay + .padding(.vertical, viewModel.verticalMargin) + .overlay { + if viewModel.isSeparateLineDisplay { + HStack(spacing: .zero) { + VStack(spacing: .zero) { + Spacer() + Divider() + Color.clear.frame(height: 1) + } + } // end HStack + } // end if + } // end .overlay + } + } // Group } } @@ -258,26 +294,17 @@ extension UserView { var headlineView: some View { Group { switch viewModel.kind { - case .account: - nameLabel - case .search: - nameLabel - case .friend: - nameLabel - case .history: - nameLabel - case .notification: - nameLabel - case .mentionPick: - nameLabel - case .listMember: - nameLabel - case .addListMember: - nameLabel - case .settingAccountSection: - nameLabel - case .plain: - nameLabel + default: + HStack(spacing: 6) { + nameLabel + if viewModel.protected { + Image(uiImage: Asset.ObjectTools.lockMini.image.withRenderingMode(.alwaysTemplate)) + .resizable() + .frame(width: lockImageDimension, height: lockImageDimension) + .foregroundColor(.secondary) + Spacer() + } + } } } // end Group } From 609fd2a8160ed56a53ece44f8f29aec71be765f4 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 May 2023 18:56:36 +0800 Subject: [PATCH 3/7] fix: make protected Twitter user timeline fetchable --- TwidereSDK/Package.swift | 2 +- .../Error/Twitter/TwitterAPIError.swift | 4 ++++ .../StatusFetchViewModel+Timeline+User.swift | 8 ++++++++ .../Status/StatusFetchViewModel+Timeline.swift | 14 ++++++++------ .../xcshareddata/swiftpm/Package.resolved | 4 ++-- TwidereX/Diffable/Misc/TabBar/TabBarItem.swift | 13 ++++++++++++- TwidereX/Scene/Profile/ProfileViewController.swift | 1 + TwidereX/Scene/Profile/ProfileViewModel.swift | 7 ++++++- .../Segmented/Paging/ProfilePagingViewModel.swift | 4 ++++ .../Root/Drawer/DrawerSidebarViewController.swift | 13 ++++++++++++- 10 files changed, 58 insertions(+), 12 deletions(-) diff --git a/TwidereSDK/Package.swift b/TwidereSDK/Package.swift index 24193981..007e3ef9 100644 --- a/TwidereSDK/Package.swift +++ b/TwidereSDK/Package.swift @@ -51,7 +51,7 @@ let package = Package( .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.6.0"), .package(url: "https://github.com/tid-kijyun/Kanna.git", from: "5.2.7"), .package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.2.0"), - .package(url: "https://github.com/TwidereProject/TwitterSDK.git", exact: "0.7.0"), + .package(url: "https://github.com/TwidereProject/TwitterSDK.git", exact: "0.8.0"), .package(name: "ArkanaKeys", path: "../dependencies/ArkanaKeys"), .package(name: "CoverFlowStackLayout", path: "../CoverFlowStackLayout"), ], diff --git a/TwidereSDK/Sources/TwidereCore/Error/Twitter/TwitterAPIError.swift b/TwidereSDK/Sources/TwidereCore/Error/Twitter/TwitterAPIError.swift index dd4d6123..1bc90779 100644 --- a/TwidereSDK/Sources/TwidereCore/Error/Twitter/TwitterAPIError.swift +++ b/TwidereSDK/Sources/TwidereCore/Error/Twitter/TwitterAPIError.swift @@ -13,6 +13,8 @@ extension Twitter.API.Error.TwitterAPIError: LocalizedError { public var errorDescription: String? { switch self { + case .notAuthorizedToViewTheSpecifiedUser: + return L10n.Common.Alerts.PermissionDeniedNotAuthorized.title case .userHasBeenSuspended: return L10n.Common.Alerts.AccountSuspended.title case .rateLimitExceeded: @@ -32,6 +34,8 @@ extension Twitter.API.Error.TwitterAPIError: LocalizedError { public var failureReason: String? { switch self { + case .notAuthorizedToViewTheSpecifiedUser: + return L10n.Common.Alerts.PermissionDeniedNotAuthorized.message case .userHasBeenSuspended: let twitterRules = L10n.Common.Alerts.AccountSuspended.twitterRules return L10n.Common.Alerts.AccountSuspended.message(twitterRules) diff --git a/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline+User.swift b/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline+User.swift index c847bfeb..c18a5d56 100644 --- a/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline+User.swift +++ b/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline+User.swift @@ -26,6 +26,7 @@ extension StatusFetchViewModel.Timeline.User { public struct TwitterFetchContext: Hashable { public let authenticationContext: TwitterAuthenticationContext public let userID: Twitter.Entity.V2.User.ID + public let protected: Bool public let paginationToken: String? public let maxID: Twitter.Entity.V2.Tweet.ID? public let maxResults: Int? @@ -37,6 +38,7 @@ extension StatusFetchViewModel.Timeline.User { public init( authenticationContext: TwitterAuthenticationContext, userID: Twitter.Entity.V2.User.ID, + protected: Bool, paginationToken: String?, maxID: Twitter.Entity.V2.Tweet.ID?, maxResults: Int?, @@ -45,6 +47,7 @@ extension StatusFetchViewModel.Timeline.User { ) { self.authenticationContext = authenticationContext self.userID = userID + self.protected = protected self.paginationToken = paginationToken self.maxID = maxID self.maxResults = maxResults @@ -56,6 +59,7 @@ extension StatusFetchViewModel.Timeline.User { return TwitterFetchContext( authenticationContext: authenticationContext, userID: userID, + protected: protected, paginationToken: paginationToken, maxID: maxID, maxResults: maxResults, @@ -68,6 +72,7 @@ extension StatusFetchViewModel.Timeline.User { return TwitterFetchContext( authenticationContext: authenticationContext, userID: userID, + protected: protected, paginationToken: paginationToken, maxID: maxID, maxResults: maxResults, @@ -157,6 +162,9 @@ extension StatusFetchViewModel.Timeline.User { switch fetchContext.timelineKind { case .status, .media: do { + guard !fetchContext.protected else { + throw Twitter.API.Error.ResponseError(httpResponseStatus: .ok, twitterAPIError: .rateLimitExceeded) + } guard !fetchContext.needsAPIFallback else { throw Twitter.API.Error.ResponseError(httpResponseStatus: .ok, twitterAPIError: .rateLimitExceeded) } diff --git a/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline.swift b/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline.swift index c871bd12..c62fcd5c 100644 --- a/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline.swift +++ b/TwidereSDK/Sources/TwidereCore/ViewModel/Status/StatusFetchViewModel+Timeline.swift @@ -50,25 +50,26 @@ extension StatusFetchViewModel.Timeline.Kind { public class UserTimelineContext { public let timelineKind: TimelineKind + @Published public var protected: Bool? @Published public var userIdentifier: UserIdentifier? public init( timelineKind: TimelineKind, - userIdentifier: Published.Publisher? + protected protectedPublisher: Published.Publisher?, + userIdentifier userIdentifierPublisher: Published.Publisher? ) { self.timelineKind = timelineKind - - if let userIdentifier = userIdentifier { - userIdentifier.assign(to: &self.$userIdentifier) - - } + protectedPublisher?.assign(to: &$protected) + userIdentifierPublisher?.assign(to: &$userIdentifier) } public init( timelineKind: TimelineKind, + protected: Bool, userIdentifier: UserIdentifier? ) { self.timelineKind = timelineKind + self.protected = protected self.userIdentifier = userIdentifier } @@ -450,6 +451,7 @@ extension StatusFetchViewModel.Timeline { return .user(.twitter(.init( authenticationContext: authenticationContext, userID: userIdentifier.id, + protected: userTimelineContext.protected ?? false, paginationToken: nil, maxID: nil, maxResults: nil, diff --git a/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved index 049913c7..33da201f 100644 --- a/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/TwidereX.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -258,8 +258,8 @@ "repositoryURL": "https://github.com/TwidereProject/TwitterSDK.git", "state": { "branch": null, - "revision": "cf2f33c96e7b9f86dbee23111cd523f9a9294099", - "version": "0.7.0" + "revision": "1b2998a2fc5b7abc421e61b075ba3807ba694991", + "version": "0.8.0" } }, { diff --git a/TwidereX/Diffable/Misc/TabBar/TabBarItem.swift b/TwidereX/Diffable/Misc/TabBar/TabBarItem.swift index fa9defd3..7c081596 100644 --- a/TwidereX/Diffable/Misc/TabBar/TabBarItem.swift +++ b/TwidereX/Diffable/Misc/TabBar/TabBarItem.swift @@ -121,7 +121,18 @@ extension TabBarItem { fatalError() case .likes: let _viewController = UserLikeTimelineViewController() - _viewController.viewModel = UserLikeTimelineViewModel(context: context, authContext: authContext, timelineContext: .init(timelineKind: .like, userIdentifier: authContext.authenticationContext.userIdentifier)) + _viewController.viewModel = UserLikeTimelineViewModel( + context: context, + authContext: authContext, + timelineContext: .init( + timelineKind: .like, + protected: { + guard let user = authContext.authenticationContext.user(in: context.managedObjectContext) else { return false } + return user.protected + }(), + userIdentifier: authContext.authenticationContext.userIdentifier + ) + ) viewController = _viewController case .history: let _viewController = HistoryViewController() diff --git a/TwidereX/Scene/Profile/ProfileViewController.swift b/TwidereX/Scene/Profile/ProfileViewController.swift index 9e6487dd..18c67e5c 100644 --- a/TwidereX/Scene/Profile/ProfileViewController.swift +++ b/TwidereX/Scene/Profile/ProfileViewController.swift @@ -57,6 +57,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, DrawerSide authContext: authContext, coordinator: coordinator, displayLikeTimeline: viewModel.displayLikeTimeline, + protected: viewModel.$protected, userIdentifier: viewModel.$userIdentifier ) return profilePagingViewController diff --git a/TwidereX/Scene/Profile/ProfileViewModel.swift b/TwidereX/Scene/Profile/ProfileViewModel.swift index 6dfb433c..ffb797e9 100644 --- a/TwidereX/Scene/Profile/ProfileViewModel.swift +++ b/TwidereX/Scene/Profile/ProfileViewModel.swift @@ -30,6 +30,7 @@ class ProfileViewModel: ObservableObject { let displayLikeTimeline: Bool @Published var userRecord: UserRecord? @Published var userIdentifier: UserIdentifier? = nil + @Published var protected: Bool? = nil let relationshipViewModel = RelationshipViewModel() // let suspended = CurrentValueSubject(false) @@ -52,7 +53,7 @@ class ProfileViewModel: ObservableObject { } $user - .map { user in user.flatMap { UserRecord(object: $0) } } + .map { $0?.asRecord } .assign(to: &$userRecord) $user @@ -67,6 +68,10 @@ class ProfileViewModel: ObservableObject { } } .assign(to: &$userIdentifier) + + $user + .map { $0?.protected } + .assign(to: &$protected) // bind active authentication Task { diff --git a/TwidereX/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift b/TwidereX/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift index 87b5d25a..8aa1be67 100644 --- a/TwidereX/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift +++ b/TwidereX/Scene/Profile/Segmented/Paging/ProfilePagingViewModel.swift @@ -27,6 +27,7 @@ final class ProfilePagingViewModel: NSObject { authContext: AuthContext, coordinator: SceneCoordinator, displayLikeTimeline: Bool, + protected: Published.Publisher?, userIdentifier: Published.Publisher? ) { self.context = context @@ -39,6 +40,7 @@ final class ProfilePagingViewModel: NSObject { authContext: authContext, timelineContext: .init( timelineKind: .status, + protected: protected, userIdentifier: userIdentifier ) ) @@ -55,6 +57,7 @@ final class ProfilePagingViewModel: NSObject { authContext: authContext, timelineContext: .init( timelineKind: .media, + protected: protected, userIdentifier: userIdentifier ) ) @@ -75,6 +78,7 @@ final class ProfilePagingViewModel: NSObject { authContext: authContext, timelineContext: .init( timelineKind: .like, + protected: protected, userIdentifier: userIdentifier ) ) diff --git a/TwidereX/Scene/Root/Drawer/DrawerSidebarViewController.swift b/TwidereX/Scene/Root/Drawer/DrawerSidebarViewController.swift index e71a8679..e4712687 100644 --- a/TwidereX/Scene/Root/Drawer/DrawerSidebarViewController.swift +++ b/TwidereX/Scene/Root/Drawer/DrawerSidebarViewController.swift @@ -210,7 +210,18 @@ extension DrawerSidebarViewController: UICollectionViewDelegate { let federatedTimelineViewModel = FederatedTimelineViewModel(context: context, authContext: viewModel.authContext, isLocal: false) coordinator.present(scene: .federatedTimeline(viewModel: federatedTimelineViewModel), from: presentingViewController, transition: .show) case .likes: - let userLikeTimelineViewModel = UserLikeTimelineViewModel(context: context, authContext: viewModel.authContext, timelineContext: .init(timelineKind: .like, userIdentifier: viewModel.authContext.authenticationContext.userIdentifier)) + let userLikeTimelineViewModel = UserLikeTimelineViewModel( + context: context, + authContext: viewModel.authContext, + timelineContext: .init( + timelineKind: .like, + protected: { + guard let user = viewModel.authContext.authenticationContext.user(in: context.managedObjectContext) else { return false } + return user.protected + }(), + userIdentifier: viewModel.authContext.authenticationContext.userIdentifier + ) + ) coordinator.present(scene: .userLikeTimeline(viewModel: userLikeTimelineViewModel), from: presentingViewController, transition: .show) case .history: let historyViewModel = HistoryViewModel(context: context, coordinator: coordinator, authContext: viewModel.authContext) From 360cf430e843afe0bce086f01b55f2100a4362f2 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 May 2023 18:56:57 +0800 Subject: [PATCH 4/7] fix: media layout not center alignment issue --- .../Container/MediaStackContainerView.swift | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/TwidereSDK/Sources/TwidereUI/Container/MediaStackContainerView.swift b/TwidereSDK/Sources/TwidereUI/Container/MediaStackContainerView.swift index 1462e880..b843376b 100644 --- a/TwidereSDK/Sources/TwidereUI/Container/MediaStackContainerView.swift +++ b/TwidereSDK/Sources/TwidereUI/Container/MediaStackContainerView.swift @@ -28,19 +28,23 @@ public struct MediaStackContainerView: View { let dimension = min(root.size.width, root.size.height) switch viewModel.items.count { case 1: - MediaView(viewModel: viewModel.items[0]) - .frame(width: dimension, height: dimension) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .overlay(alignment: .bottom) { - MediaMetaIndicatorView(viewModel: viewModel.items[0]) - } - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(Color(uiColor: .placeholderText).opacity(0.5), lineWidth: 1) - ) - .onTapGesture { - handler(viewModel.items[0], .preview) - } + VStack { + Spacer() + MediaView(viewModel: viewModel.items[0]) + .frame(width: dimension, height: dimension) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay(alignment: .bottom) { + MediaMetaIndicatorView(viewModel: viewModel.items[0]) + } + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color(uiColor: .placeholderText).opacity(0.5), lineWidth: 1) + ) + .onTapGesture { + handler(viewModel.items[0], .preview) + } + Spacer() + } default: CoverFlowStackScrollView { HStack(spacing: .zero) { From 9babc24dac19e76fb5e46b29fa7b259e69c3095d Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 May 2023 20:45:22 +0800 Subject: [PATCH 5/7] fix: status repost not respect user protected and post visibility issue --- .../text.bubble.imageset/Contents.json | 15 +++ .../text.bubble.imageset/message.pdf | Bin 0 -> 4802 bytes .../text.bubble.mini.imageset/Contents.json | 15 +++ .../message.mini.pdf | Bin 0 -> 4795 bytes .../Media/repeat.lock.imageset/Contents.json | 15 +++ .../repeat.lock.imageset/lock and repeat.pdf | Bin 0 -> 5862 bytes .../repeat.lock.mini.imageset/Contents.json | 15 +++ .../lock and repeat.pdf | Bin 0 -> 5818 bytes .../Contents.json | 0 .../repeat-off.pdf | Bin .../repeat.off.mini.imageset/Contents.json | 15 +++ .../repeat.off.mini.imageset/Retweet-Off.pdf | Bin 0 -> 5893 bytes .../TwidereAsset/Generated/Assets.swift | 7 +- .../TwidereUI/Content/StatusToolbarView.swift | 116 +++++++++++++----- .../Content/StatusView+ViewModel.swift | 68 ++++++---- 15 files changed, 214 insertions(+), 52 deletions(-) create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.imageset/Contents.json create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.imageset/message.pdf create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.mini.imageset/Contents.json create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.mini.imageset/message.mini.pdf create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.imageset/Contents.json create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.imageset/lock and repeat.pdf create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.mini.imageset/Contents.json create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.mini.imageset/lock and repeat.pdf rename TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/{repeat-off.imageset => repeat.off.imageset}/Contents.json (100%) rename TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/{repeat-off.imageset => repeat.off.imageset}/repeat-off.pdf (100%) create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.mini.imageset/Contents.json create mode 100644 TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.mini.imageset/Retweet-Off.pdf diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.imageset/Contents.json new file mode 100644 index 00000000..8b028c9a --- /dev/null +++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "message.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.imageset/message.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.imageset/message.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f9a4bb81af12d0e964fdb24551a048d684ec9d4d GIT binary patch literal 4802 zcmdT|O^@6}5WV-W=u4#J5RbdvUzVasWJ3@FMA&ePI1IB+cF}ym&IAR1eO}q^*S%{? zhyw?Fh@9u{s#jI7s(1Ry+t+WNn^wqoif4j5#Lz$)>AKlI`&-uo0Y5r!q`E7SlT(^8V z?~dg&_Z$>#dv9&>njj*~4D9@^hVr+aDxhhci_v&ptkDLk5ZSE|*{fSsVw(LQfMs^v z-OnHCl!%=_?No(j1GL0A>5s^6g@Z6##vsgcBFJoCFk0M~y=z#t_f~ySNA=Uc762pt z-`77ZGI~&9QU6$0Wzi9->R(~G{xW<{4J}xUjMa~#Axw9t3N%oP48{j8vW1p zH$}5x>SE}7eNcu9GDP=kZLPFI!C`D^f?#c-0%5?-8U|I-#S5BZ+feDl`(n{$D{+D&BQ@>d^EAWB4(P9 zqO(c6#`Il;ZPRpc)_NemjZ!t!m>2=Ut!t8PlW8&ddwGPDZM_d3tJ>Bjmw31Husg+Itk)1XCR7BD{Sc)MHbNb+8QO$;TV!JLUYej_P$(->7>&KL!T`+? znwWZ#JRjET0x(+i$<_0T` z#>FT>N#UGH!GsCDGtdF4ho(n4I;4&esf_k=zY=^{WcnU!ob?zQ>yv}@X4$$j|B7zv z0#i&?gG&A;EfSP6OpaDOZ;f*Tp5jkv84XH)ylv2_j_#=^G%ouY%R~Tm1F+ zc&HyRT{C8_|)gi&X)hMp8 z>eyk3)kr0_Umde+IU`o>tp+#IgR)X)A7Z5+NEi*!1DQqEcyxS{HSB{HXPS{QKvBrR z?JdwAC*Q1{rpEw<_E}FJ3Pg4#D)Jh&wD1Zkq37CYoGwi|=RJ3_oSL~3Y^eHY48eVM z>f{#f#@{y6bjyt#aZo=F6zX44n+QL4EQB*w&#H3Vt)mr3?yoQ$VN0tIUCqOOPM5TG zJ`-l@fOvc{#`^S<-+7V?44KsANnbJNiBMszpQLCdaABUMRNM>mtT=%An58V?JNFZn zQ=m#Z<>W+r&#Eh6mgYE@^GUoCbXD2HPfEMu8?@PFg;71jiubBzkVO|=I!?5kbxhDo4f6+(`Ws@ z+&*v^Th6iaHe0$L&ZkfJyWy1XyE+{&!x0|kT4V;_ZtsC#H8 literal 0 HcmV?d00001 diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.mini.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.mini.imageset/Contents.json new file mode 100644 index 00000000..fffdd98b --- /dev/null +++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.mini.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "message.mini.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.mini.imageset/message.mini.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Communication/text.bubble.mini.imageset/message.mini.pdf new file mode 100644 index 0000000000000000000000000000000000000000..78dfc7cdcc0dfdbb6635ec19a26549086dae48b3 GIT binary patch literal 4795 zcmdT|OOM+&5WeeI@UlR1h)hy^h(Hja*)&Db7EPVrf*xG2l5F8eYdb~SU!QL%YB*kL zf%ecteOPcmk@KE!B;LGv_4=7P!{LtpZLGvs-w%fmmyfCg zxTam_%W2r`o7Jn~&-?AL{^q5+eJlUlo|``!)9USNp5d`RsZh<3(V;}nB{;`rFKX?!N3NgW;KeY*Ft52{MzXTp@Sd%IxBuKC!E zE_DdjBzF%iy69c3SZH-mt}BFMR4Nd*O<=_Zmc)LtK1Q|+Hjrg=AILJYLR!147)#vM zvr8CzN?P7Ghvuh$oqyj_IBc)}_xX2IxpP$ZjT@^|)P$RggYz#C&cDD){#g>|f%P%6 zg+A`RQmv2We`zOUEb%|fzpaM4fd2g_`8RyX=(Z2Z7!3vhI<+yQJ3>n&>Rt*(#eM5L zA7i`{!Jw2NKnUPw>VyCWVGOz&QXzmr-bogOHqb9BM)wtSt(}esh}W@mim}jkDZ*w# zT;JnSz;+#z2V!F1r>M477Rx8C-PG=;T5VfDQBeuSt`V3G-bdoqX!{U*A}kZE4?t4r zdYwROu)-&sFZ&PweD?NA4V@~6A zZ@Syu+$YeaIQf0lFR|hlw&!cBywBR@>GEZ38?3`=yvHRtw%|`)85L_AgkF z?JstTbXT($LCVGGL5}szuWM~A;#!&2&Z5O?{2UUDNFjV2Xx9dW^g*Uk{mEFo8DtKm zphQgsqeJpw0gfo->EJpmAT}je&fM=@q$PN)8)kDo(v(93XB$h|a@*FrVMrE}Xi)4d zwa6gCP)fFIb@X1t6V5JsER)OjmD((o%(@?A?ZWP?Sq_3lkR_6$IinK?C4?kc5N~1m z9T@B(m+qR_YHvM=7Ogt+>uy0FX<;s9)?MaTo(9Z_R}+F6doGW(Fw3%sf=?MEi6yoo zJbZQu6K4f$Gm3)3jO`s*i80f5Ui*$_XRa_BIK~8H2;n9GGkNbY;9Yr18d88Ir9=4*(u3Y|8w;dFeJeg-xbJoXgA}Va;^8yo^h@YmnJKlyYjl$|b^d<3N`S z<4uG!-tk3*V;C0{tFI1+0{k%C-EUqVKdX1BkKzC? z3_hpxPu0~VX+4~epHAE1tmsN)UBulU50~KpjdUL}fp0b^U<&WA84|Z91o3Uk>bv_p zn5xKwwVL$Y9zWr%nc05^A|!VV&OManJYFuQU6<45em9&N%HFrH5RND3<9>Lm rp4{I(9fPu3ACDJB4(9-`-roN`!1{d6v^ia}JI=?hxw-lJ$Jbu~b_csL literal 0 HcmV?d00001 diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.imageset/Contents.json new file mode 100644 index 00000000..bcacd54f --- /dev/null +++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "lock and repeat.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.imageset/lock and repeat.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.imageset/lock and repeat.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6b2ad0cbf1ccfb03553e0c4815c4e46afc803583 GIT binary patch literal 5862 zcmb7I+m0MH5Pk2j@JpoRA-(Ne+bu>#90cu8ZwQZ(hBAs{7D&S`F&>>u;S>&z`C0&%5p6R{q(U7Qgy_d-(A1QCWc3 zvg-73zrERaSFg5z-fgzm-@H&S-?snSoVq_c-4E%fhWhc8U;LY^f1CPgFg7@~*>_yF z{5Wk69heu*rXQv-no;c=Q1{+>>$HM7?~RFGK_IWALLwVjyIe*6R7HdqH;qMtyY6FW zE4f&W?q_VO*lxzSPNem?A`UPNK@U-d-cEzpliGDUBwbbm3{elxsu_yC)6PI(7L9!z zT(CxM){G?EHk%1b>$-T~Y1>;p`Uv%=*ONCMe>eRY5#sbUV`egO!a7RBlur9PPJ&iW zGd7UA>8tcmeT4K1qr{Y}eM6LL{nCX=Pa0Wc@?BI_Pa_q81ki}6hE^wnmBl5L2on?$f>c{b`0YP#77@u0OGZdc0yVV zy$;$3i-elYICzUJlNt(GX#ncrry;1BGPL>VCeuKqfz4Y`%YnHxEQF_Vh<*Z@7{^H) zWqLPGehjc$DwkzNWHXmgQ&!MrCvBY~HozM=7FF!wx}G8$(xs!q_L7&dU0MZOmME!U zcL>sMQ{rn_BaCfiLTjc!Ecsd#ZQZjOu%cmpwNTcyrahous(>+4XcBqW%al*PuC$}2 zPBHbqJ9IyN@h})tRXZf*e+~oWFNVRWQIn|=Y0LtI)*I`_84L5O(U~bFR*~~nZUZ@D z!3A$eNx!TKsV=QgOTA=!L^d5UauC-!YiI#e3Jp&>c~Cy}Mbsv043&cMgih{rF}hSx z8FGCLaYX-|MP2V=)Xt}pEt8No#4>jhEjO>+1FIc7SJa?NgEMtH3R1{XZqs2vXCi3o znFtY6*;39_r#34bgc(4Qpf@{~0Nn z!H~fCN~?;d1}XOH9BVn0OwJb2uoE+(h8PzR6E~qK?-}GuQQ+%ZBzfWk`J6~FMQpdS zE=!9{OKOjm#ELt95g1x!5Tt!w28KLFQ6M315C*2PS1@75u}=%tFYF$%UGWuI?z;GB z&NypWBU{V0)hJd*8_`{7E6f^Dt7}-pj?XaX@xP9Bt}!FFZ>&%>;tZ1OE5RGOuewfowgr@ zrhF7i%vK_`;IScOA$!OL^enaRY@ykQnae6jX&EJJaUn|aGdm@Z87Wo;Jt5L~!kkS5 zEXRP@%osbBlYNHBLhc8JFENQrcIdnl5o`Jw+x8m96p<+>A#L@YS)sHXY065RVy*T@ z%K^aZU;+;R+D&eBKyY}{P7oVy#34bVf=iLZ-3P7PIp;AFr1jwLinjzT#4R@BicP{e zO_<5zyhVg0WU7!vN(Cun3Cg8e84h|FJa(fCi?!hOo5NxSe=7+-P|#jmMFhpWFf_M0WPUA>_Dl z3BmyTew>`gTTUBboKQo=ZLy(;(@B_YzyPNcq%lKH1jtPoFD(n>h-0qxU{ex+LyAe# zAZ5pDyF-k*vvoiLe$m1Qrl zr;_u2mg5YJMO`ib3wG8EDNVE<%g(K^CpA_myM|Gp_>%9A@+4W_8|&kwyhG+kOFZvs zzF9iLc+0%{>TozdoYcEt@V1HH)z5$ZzExLmZa!=a@Wb}@?&ihuvwA0UA^qkLgWS*1 z{8cqwZ%@Zh_nYlW$?NHLQTOtAc-S6bQJzjK_~zywnDPZ`9#{q9!|T;|cekLb#Di;9 z?YumGf~;QfKLKepjc1y~!4nP6j=#KlxY->)oQrzerQkW zOC1Q|b>$rLRwESh<0_m77OOn&y%(!+Y$x<^e{;9n-gmNl-@ZaP9-of;?GyF*?)FIz m%GLGp_@Kn$6yU43cYkleKD|ia+&?6D{OE@6(W9?_eEkomZia0D literal 0 HcmV?d00001 diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.mini.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.mini.imageset/Contents.json new file mode 100644 index 00000000..bcacd54f --- /dev/null +++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.mini.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "lock and repeat.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.mini.imageset/lock and repeat.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.lock.mini.imageset/lock and repeat.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f148daea4478627d732b3238ac2ef5d9d23b6bd1 GIT binary patch literal 5818 zcmb7IOOG5y48HfTs7s{eke+%!q$m>E5QG2`Hryf(!|WtkG!K}Wpun%^b5*sw`XQn8 zVb%Lh+5YUZUG94F_Vt_Rst;YKgcRrB|LBBx@j|?O+3imc^lztIeDmY}^zrsd7=ZV* z>Uz8Ex5sYtdjI$1Zh!yXD{=R({cm^e{_0dO^CRT&@|@rJH{E+Y&I{jL?L#nPckFoC z^5wcabx;!=sTQht!Dt=CaRMrrq%s2Qtg>2JFo-&@YZO*w2W!h!tiP&STHH-6RvL%y zQ>O=S-AIlx-lX-8(P&nzsMAs{SgMhnbemn zHY6am9=TK)*^Q;rl#l9lkltA|j#^R@(N-!dAkr9{95tSqqsaXQFWSbXRj}n!WGbjS z2H9>?%4=97+$1ume6~|FYg*m&x;?BG2d6k|B!57&>g2MgT3%-GGShQsS!p**jb!LU zcj|um>SiyI2*x4D{=eBH_wWQObRVe!S@e3_BiUVyBZkPuyvp>{U#+Ax9uRM{+^%EH z_R^z0MxkZyT6!%5Rbn8V61G|AEEg)$8J(4n{^kr}hS zi!MYDg~4fy(GaKFF!w=>F0JGNu$ouG4kO=L_H0tL}H2^R@N11YF&_KmPf3t82e%nyQI+RI0kH$5o%qC zoC5*ImF&K<5 zys~}4Pm*ScnjWlxTK+i06q<(-qXvP=ohaoOnuzAh85xd&si(wtZZ)$liHfQ%PB&K9 zFlJ1#o9invsVGZNtZ0SNEvJ5o5_)GLz=|1Z{NTxr*wqLy#*SlZi}*HcHj_Ez{91V0 zTH>)>vWbi4~)j2_DCP3Q<`{cLhELn~+K}24`vH#|JR| zMCSlLWAGV-&h1T#(qjTc_>5Ok0@F4rNuLI_>wCmHX&f63!nwz6nHR-mX_d|f8ktQ5 z6Bu_^gPXDV#362+Ta-^YD;S|6r((^S0;~9%!@b-I zJGV+~@#Tb`IWoayBhg+>%ReauD*@ry8ti~kp5n-{#g|~z)}@gf&MK#!b0hzymyPEf zjly#2U$O;!Ex2$0n!NA1WFpB`=cGsN5Ygi+jM^|KLtC3pMvRN|L zXe|3XwanPWN*O8Xnz7y?4i}Dew^%MB=}v<0A#ry%-cN46Ii1e8t9bt#?kn+Ze*Ndq zz1Y0ne%x2!r~Sj@_SN}|c#j}tcPJY5Md4x~KVp0vG~Ms7=g*hj{wnB3^uDUQJD+a* z6D-no=m5UmUVsVokPJ1KrUdb2>gI>X2T&E{!Mzyl+?_u|RxS8nfi#*h^fsEv!7Cda z9lzV&wuke_spyx>{zHcf?TWATS9pidZ2`DGn4|KWzzAL+1D^CSdfA0M8Tplt5X=bIphbAUJR9{=5geZFPh SUT)bP=aTQ9Jo)zLH~#_bNQ0XI literal 0 HcmV?d00001 diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat-off.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.imageset/Contents.json similarity index 100% rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat-off.imageset/Contents.json rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.imageset/Contents.json diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat-off.imageset/repeat-off.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.imageset/repeat-off.pdf similarity index 100% rename from TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat-off.imageset/repeat-off.pdf rename to TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.imageset/repeat-off.pdf diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.mini.imageset/Contents.json b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.mini.imageset/Contents.json new file mode 100644 index 00000000..1fe64dc8 --- /dev/null +++ b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.mini.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Retweet-Off.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.mini.imageset/Retweet-Off.pdf b/TwidereSDK/Sources/TwidereAsset/Assets.xcassets/Media/repeat.off.mini.imageset/Retweet-Off.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a4f1818af305724ffb419f2bc74eb49581028150 GIT binary patch literal 5893 zcmd^DOOMn>5Wf3Y_<{rnJpF!1XeD4_6(td5HwPq?!!Q_j$vieQLj?Kt`Kr5J?y)BV z&Y8=4KX*O4y1uG*J$QZj^0DlE)5un|yLX?Owte!XUHty_zHb+AUcLL&ua3a*Tl~^* zKOS#eg@xAzy4vqP-&X?Z|NJh=WWgKg+b_3=`{lmhuD+hUzqlx>Za%*|CZjKR`%S^F z`@`<@e$^ky1h0JdZ1<&o+lo%4-}oTi0F6(7Koukp$9=!tG>gmr@4Hohef6w;epCOq zIy8SZau8?LkNIi-j$f@d4L{Ex4y*0>dh~14jL+lKxsr?t<-FJJw@)3(<1M9VD?978Qoh|Z zH7IQqQn!GNG)f}GUBKaB9iKBJDFRR&`Pc7ZeaLoVSPsbVaYD42X_|hNx)AR+=DyXc-^? zL|QM6q%{$$RFa$eK%SX_hLFwS*fhGc!TR7yU&eH&a&T=I16-7Cg_M1+Ajhjph*%T$ zm^HCIMpkEhNHG&B4V=g3`9vjl79FAmP#6{P02<5Agczcrz-jC8P(wiq=L|tblme%% z^%Ru35wabJrD0gQX+jr;k;1g0)6p3#@L*I_njRRD+G*{9unFGj>L&v@s@oc&*pTXQe8-c zM98FER}?u5GJUBMGv!#ivcuw)9eAWdKCK<`I!&o)YuaNmM<<-?>IIl)LM9-W0fJpmKk2&!8e9(P=!WSyvba zORd8?5kx?N1<`XE8_$ZNR7{jr#-%YP2cS>EJzj6V!nW(owro z&IY_o8%9qOl0a#6Fg8_LuY6LbvCd0^R5YLlR(U7AqHZ+zgLWJ{<05u|#uts!Xqe<( za2BjX%Mnfs0dAvXhiHrkWP%bhI~;;GK!Oh{m}~-X6`4g_wI*O&RIo(=Vq$8Pu@f53 z86X-L4HzWXrcRJ+VdzFoc*=Slj#ZAN^Lekwb>}daC5?RVU^LxW5KxVtQq@+}pVU1y z?Jl>F8Y(>mP!X+4_1s2EEZU*-bXO`(6r}=ZgVeztHCHjZd?@V=npuSu3oN(7HKzC` zhgMS|#alIaM)#Sa^Ui!A&v+%*E4nBZ3*6l+WNLwH5_wkqM>)+6oA+2DB@!ocWtZ#& zdBz;8Y6W8z#YQfl1zEacZG}~Kn9D=~@Z0+U(jx1T!^mV@ctpAe;SP(^Ldb;Jsjf_! zmrAhqAh3oup-hk8M5+og;HF$4u080fi5c@cLmp2#lrBrsm`Ei><07jgN~ez! z_n^!b0ftW5<6wa>i&~51D;Gv$RMf=sgsCVk_NoMwDMC;ZJRwug5|5{T(K#}YnV;1c zC1cfR;E5-t9-qT1*(#4Y&AU znIG)LJSxyuTYS!Z`|FL(N|ZhCJhFr0Rrm`qvDDMG>l{}0F$$*-v7s)ZXa*gkBIoWmC-YPaOllPzW_*+DzZX40oz$s>H6 zbj2TtCLak(ZYOO;6lb4MFJ$_2^Xu~PXL`yb zGy3;rz%|anPM;Q6Ee&7FB<=Lw?7sy@XiwMox65y<9>!!d9R!rQ9`EG8)LmTUZ9~~G zDRL)~DziW|;vAvxny)PovyX>nfzV#=&QGaGVL2wprUQd?NKd!h-SN=ArODp7rPs@k zeF1*$-`_2NbVf*;#&IL1#W3T1zS|!AEi6(52JkhFGLLO6P!%VF@(#$^pYPto)s}W} z-3}f<-+hLxocVtMQfZRnL>2Auz!t}yKVKe~>)ppw*)RM4LqjdI$(1jSp`1nWYk`*^ z8AvZMq0B#nHameVuF|KF2^h9aAxtw)AxjtY38dz$=#D>GWh}x)zjp8oevi72`{mub z-!~NApDq#Hhlky!f7Cv_d;chhcyYbkA-)v8bY6>_yMKC$1%i7xF89Y29c!a%9z1yQ G>gB)Q4YZK} literal 0 HcmV?d00001 diff --git a/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift b/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift index d244bd3b..1730b999 100644 --- a/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift +++ b/TwidereSDK/Sources/TwidereAsset/Generated/Assets.swift @@ -87,6 +87,8 @@ public enum Asset { public static let ellipsisBubblePlus = ImageAsset(name: "Communication/ellipsis.bubble.plus") public static let mail = ImageAsset(name: "Communication/mail") public static let mailMiniInline = ImageAsset(name: "Communication/mail.mini.inline") + public static let textBubble = ImageAsset(name: "Communication/text.bubble") + public static let textBubbleMini = ImageAsset(name: "Communication/text.bubble.mini") public static let textBubbleSmall = ImageAsset(name: "Communication/text.bubble.small") } public enum Editing { @@ -148,9 +150,12 @@ public enum Asset { public static let altRectangle = ImageAsset(name: "Media/alt.rectangle") public static let gifRectangle = ImageAsset(name: "Media/gif.rectangle") public static let playerRectangle = ImageAsset(name: "Media/player.rectangle") - public static let repeatOff = ImageAsset(name: "Media/repeat-off") public static let `repeat` = ImageAsset(name: "Media/repeat") + public static let repeatLock = ImageAsset(name: "Media/repeat.lock") + public static let repeatLockMini = ImageAsset(name: "Media/repeat.lock.mini") public static let repeatMini = ImageAsset(name: "Media/repeat.mini") + public static let repeatOff = ImageAsset(name: "Media/repeat.off") + public static let repeatOffMini = ImageAsset(name: "Media/repeat.off.mini") } public enum ObjectTools { public static let bell = ImageAsset(name: "Object&Tools/bell") diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusToolbarView.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusToolbarView.swift index 00d5c0ea..17992bac 100644 --- a/TwidereSDK/Sources/TwidereUI/Content/StatusToolbarView.swift +++ b/TwidereSDK/Sources/TwidereUI/Content/StatusToolbarView.swift @@ -68,9 +68,9 @@ extension StatusToolbarView { image: { switch viewModel.style { case .inline: - return Asset.Arrows.arrowTurnUpLeftMini.image.withRenderingMode(.alwaysTemplate) + return Asset.Communication.textBubbleMini.image.withRenderingMode(.alwaysTemplate) case .plain: - return Asset.Arrows.arrowTurnUpLeft.image.withRenderingMode(.alwaysTemplate) + return Asset.Communication.textBubble.image.withRenderingMode(.alwaysTemplate) } }(), count: isMetricCountDisplay ? viewModel.replyCount : nil, @@ -78,50 +78,99 @@ extension StatusToolbarView { ) } + enum RepostButtonImage { + case repost + case repostOff + case repostLock + + func image(style: StatusToolbarView.Style) -> UIImage { + switch self { + case .repost: + switch style { + case .inline: return Asset.Media.repeatMini.image.withRenderingMode(.alwaysTemplate) + case .plain: return Asset.Media.repeat.image.withRenderingMode(.alwaysTemplate) + } + case .repostOff: + switch style { + case .inline: return Asset.Media.repeatOffMini.image.withRenderingMode(.alwaysTemplate) + case .plain: return Asset.Media.repeatOff.image.withRenderingMode(.alwaysTemplate) + } + case .repostLock: + switch style { + case .inline: return Asset.Media.repeatLockMini.image.withRenderingMode(.alwaysTemplate) + case .plain: return Asset.Media.repeatLock.image.withRenderingMode(.alwaysTemplate) + } + } // end switch + } // end func + + static func kind( + platform: Platform, + isReposeRestricted: Bool, + isMyself: Bool + ) -> Self { + switch platform { + case .twitter: + if isMyself { return .repost } + if isReposeRestricted { return .repostOff } + return .repost + case .mastodon: + if isReposeRestricted { + return isMyself ? .repostLock : .repostOff + } + return .repost + case .none: + return .repost + } // end switch + } + } + public var repostButton: some View { ToolbarButton( handler: { action in + guard viewModel.isRepostable else { return } logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): repost") handler(action) }, action: .repost, image: { - switch viewModel.style { - case .inline: - return Asset.Media.repeatMini.image.withRenderingMode(.alwaysTemplate) - case .plain: - return Asset.Media.repeat.image.withRenderingMode(.alwaysTemplate) - } + return RepostButtonImage.kind( + platform: viewModel.platform, + isReposeRestricted: viewModel.isReposeRestricted, + isMyself: viewModel.isMyself + ).image(style: viewModel.style) }(), count: isMetricCountDisplay ? viewModel.repostCount : nil, tintColor: viewModel.isReposted ? Asset.Scene.Status.Toolbar.repost.color : nil ) + .opacity(viewModel.isRepostable ? 1 : 0.5) } public var repostMenu: some View { Menu { - // repost - Button { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): repost") - handler(.repost) - } label: { - Label { - let text = viewModel.isReposted ? L10n.Common.Controls.Status.Actions.undoRetweet : L10n.Common.Controls.Status.Actions.retweet - Text(text) - } icon: { - let image = viewModel.isReposted ? Asset.Media.repeatOff.image.withRenderingMode(.alwaysTemplate) : Asset.Media.repeat.image.withRenderingMode(.alwaysTemplate) - Image(uiImage: image) + if viewModel.isRepostable { + // repost + Button { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): repost") + handler(.repost) + } label: { + Label { + let text = viewModel.isReposted ? L10n.Common.Controls.Status.Actions.undoRetweet : L10n.Common.Controls.Status.Actions.retweet + Text(text) + } icon: { + let image = viewModel.isReposted ? Asset.Media.repeatOff.image.withRenderingMode(.alwaysTemplate) : Asset.Media.repeat.image.withRenderingMode(.alwaysTemplate) + Image(uiImage: image) + } } - } - // quote - Button { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): quote") - handler(.quote) - } label: { - Label { - Text(L10n.Common.Controls.Status.Actions.quote) - } icon: { - Image(uiImage: Asset.TextFormatting.textQuote.image.withRenderingMode(.alwaysTemplate)) + // quote + Button { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): quote") + handler(.quote) + } label: { + Label { + Text(L10n.Common.Controls.Status.Actions.quote) + } icon: { + Image(uiImage: Asset.TextFormatting.textQuote.image.withRenderingMode(.alwaysTemplate)) + } } } } label: { @@ -190,6 +239,7 @@ extension StatusToolbarView { // input @Published var platform: Platform = .none @Published var style: Style = .inline + @Published var replyCount: Int? @Published var repostCount: Int? @Published var likeCount: Int? @@ -197,6 +247,13 @@ extension StatusToolbarView { @Published var isReposted: Bool = false @Published var isLiked: Bool = false + @Published var isReposeRestricted: Bool = false + @Published var isMyself: Bool = false + + var isRepostable: Bool { + return isMyself || !isReposeRestricted + } + public init() { // end init } @@ -208,6 +265,7 @@ extension StatusToolbarView { case inline case plain } + public enum Action: Hashable, CaseIterable { case reply case repost diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift index a338aa46..6357e160 100644 --- a/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift +++ b/TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift @@ -55,9 +55,10 @@ extension StatusView { @Published public var authorName: MetaContent = PlaintextMetaContent(string: "") @Published public var authorUsernme = "" - @Published public var authorUserIdentifier: UserIdentifier? + public let authorUserIdentifier: UserIdentifier? @Published public var protected: Bool = false + public let isMyself: Bool // static let pollOptionOrdinalNumberFormatter: NumberFormatter = { // let formatter = NumberFormatter() @@ -65,7 +66,6 @@ extension StatusView { // return formatter // }() // -// @Published public var userIdentifier: UserIdentifier? // @Published public var authorAvatarImage: UIImage? // @Published public var authorAvatarImageURL: URL? // @Published public var authorUsername: String? @@ -123,9 +123,6 @@ extension StatusView { default: return true } } - -// @Published public var isRepost = false -// @Published public var isRepostEnabled = true // poll @Published public var pollViewModel: PollView.ViewModel? @@ -149,9 +146,7 @@ extension StatusView { return nil } } -// @Published public var replySettings: Twitter.Entity.V2.Tweet.ReplySettings? -////// // @Published public var groupedAccessibilityLabel = "" // timestamp @@ -170,6 +165,9 @@ extension StatusView { guard let authorUserIdentifier = self.authorUserIdentifier else { return false } return authContext.authenticationContext.userIdentifier == authorUserIdentifier } + + // reply settings banner + @Published public var replySettingBannerViewModel: ReplySettingBannerView.ViewModel? // conversation link @Published public var isTopConversationLinkLineViewDisplay = false @@ -188,22 +186,23 @@ extension StatusView { self.authContext = authContext self.kind = kind self.delegate = delegate + let _authorUserIdentifier: UserIdentifier = { + switch author { + case .twitter(let author): + return .twitter(.init(id: author.id)) + case .mastodon(let author): + return .mastodon(.init(domain: author.domain, id: author.id)) + } + }() + self.authorUserIdentifier = _authorUserIdentifier + self.isMyself = { + guard let myUserIdentifier = authContext?.authenticationContext.userIdentifier else { return false } + return myUserIdentifier == _authorUserIdentifier + }() // end init viewLayoutFramePublisher?.assign(to: &$viewLayoutFrame) -// // 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, @@ -273,6 +272,8 @@ extension StatusView { self.author = nil self.authContext = nil self.kind = .timeline + self.authorUserIdentifier = nil + self.isMyself = false // end init viewLayoutFramePublisher?.assign(to: &$viewLayoutFrame) @@ -1097,6 +1098,16 @@ extension StatusView.ViewModel { viewLayoutFramePublisher: viewLayoutFramePublisher ) } + + // reply settings + replySettingBannerViewModel = status.replySettings + .flatMap { object in Twitter.Entity.V2.Tweet.ReplySettings(rawValue: object.value) } + .flatMap { replaySettings in + ReplySettingBannerView.ViewModel( + replaySettings: replaySettings, + authorUsername: status.author.username + ) + } // author status.author.publisher(for: \.profileImageURL) @@ -1109,7 +1120,6 @@ extension StatusView.ViewModel { .assign(to: &$authorUsernme) status.author.publisher(for: \.protected) .assign(to: &$protected) - authorUserIdentifier = .twitter(.init(id: status.author.id)) // timestamp switch kind { @@ -1185,6 +1195,7 @@ extension StatusView.ViewModel { // toolbar toolbarViewModel.platform = .twitter + toolbarViewModel.isMyself = isMyself status.publisher(for: \.replyCount) .map { Int($0) } .assign(to: &toolbarViewModel.$replyCount) @@ -1194,6 +1205,8 @@ extension StatusView.ViewModel { status.publisher(for: \.likeCount) .map { Int($0) } .assign(to: &toolbarViewModel.$likeCount) + status.author.publisher(for: \.protected) + .assign(to: &toolbarViewModel.$isReposeRestricted) if case let .twitter(authenticationContext) = authContext?.authenticationContext { status.publisher(for: \.likeBy) .map { users -> Bool in @@ -1270,8 +1283,7 @@ extension StatusView.ViewModel { .assign(to: &$authorUsernme) status.author.publisher(for: \.locked) .assign(to: &$protected) - authorUserIdentifier = .mastodon(.init(domain: status.author.domain, id: status.author.id)) - + // visibility visibility = status.visibility @@ -1337,6 +1349,7 @@ extension StatusView.ViewModel { // toolbar toolbarViewModel.platform = .mastodon + toolbarViewModel.isMyself = isMyself status.publisher(for: \.replyCount) .map { Int($0) } .assign(to: &toolbarViewModel.$replyCount) @@ -1346,6 +1359,17 @@ extension StatusView.ViewModel { status.publisher(for: \.likeCount) .map { Int($0) } .assign(to: &toolbarViewModel.$likeCount) + toolbarViewModel.isReposeRestricted = { + switch status.visibility { + case .public: return false + case .unlisted: return false + case .direct: return true + case .private: return true + case ._other: + assertionFailure() + return false + } + }() if case let .mastodon(authenticationContext) = authContext?.authenticationContext { status.publisher(for: \.likeBy) .map { users -> Bool in From 0a770283486c6059591b3164e7033fd3ae2378b3 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 May 2023 20:52:48 +0800 Subject: [PATCH 6/7] feat: add missing Twitter post reply setting banner --- .../TwidereUI/Content/StatusView.swift | 55 ++++--- .../Control/ReplySettingBannerView.swift | 139 ++++++++++-------- 2 files changed, 117 insertions(+), 77 deletions(-) diff --git a/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift b/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift index ed1a2cf1..d318ecd0 100644 --- a/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift +++ b/TwidereSDK/Sources/TwidereUI/Content/StatusView.swift @@ -209,28 +209,50 @@ public struct StatusView: View { // metric if let metricViewModel = viewModel.metricViewModel { StatusMetricView(viewModel: metricViewModel) { action in - + // TODO: } .padding(.vertical, 8) } // toolbar if viewModel.hasToolbar { - toolbarView - .overlay(alignment: .top) { - switch viewModel.kind { - case .conversationRoot: - VStack(spacing: .zero) { - Color.clear - .frame(height: 1) - Divider() - .frame(width: viewModel.viewLayoutFrame.safeAreaLayoutFrame.width) - .fixedSize() - Spacer() + VStack(spacing: .zero) { + toolbarView + .overlay(alignment: .top) { + switch viewModel.kind { + case .conversationRoot: + // toolbar top divider + VStack(spacing: .zero) { + Color.clear + .frame(height: 1) + Divider() + .frame(width: viewModel.viewLayoutFrame.safeAreaLayoutFrame.width) + .fixedSize() + Spacer() + } + default: + EmptyView() } - default: - EmptyView() + } + if viewModel.kind == .conversationRoot, + let replySettingBannerViewModel = viewModel.replySettingBannerViewModel, + !replySettingBannerViewModel.shouldHidden + { + HStack { + ReplySettingBannerView(viewModel: replySettingBannerViewModel) + Spacer() + } + .background { + Color(uiColor: Asset.Colors.hightLight.color.withAlphaComponent(0.6)) + .frame(width: viewModel.viewLayoutFrame.safeAreaLayoutFrame.width) + .overlay(alignment: .top) { + // reply settings banner top divider + VStack(spacing: .zero) { + Divider() + } + } } } + } // end VStack } } // end VStack .padding(.top, viewModel.margin) // container margin @@ -247,13 +269,12 @@ public struct StatusView: View { .frame(height: 1) } case .conversationRoot: + // cell bottom divider VStack(spacing: .zero) { Spacer() Divider() .frame(width: viewModel.viewLayoutFrame.safeAreaLayoutFrame.width) .fixedSize() - Color.clear - .frame(height: 1) } default: EmptyView() @@ -314,7 +335,7 @@ extension StatusView { } }() nameLayout { - HStack { + HStack(spacing: 4) { // name LabelRepresentable( metaContent: viewModel.authorName, diff --git a/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift b/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift index f8acb018..aedfc955 100644 --- a/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift +++ b/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift @@ -6,76 +6,95 @@ // import UIKit +import TwitterSDK import TwidereAsset +import TwidereLocalization -final public class ReplySettingBannerView: UIView { +public struct ReplySettingBannerView: View { - let topSeparator = SeparatorLineView() - - let overflowBackgroundView = UIView() - - let stackView = UIStackView() - - public let imageView = UIImageView() - - public let label: UILabel = { - let label = UILabel() - label.font = .preferredFont(forTextStyle: .callout) - label.numberOfLines = 0 - return label - }() - - public override init(frame: CGRect) { - super.init(frame: frame) - _init() - } + public let viewModel: ViewModel + + @ScaledMetric(relativeTo: .callout) private var imageDimension: CGFloat = 16 + - public required init?(coder: NSCoder) { - super.init(coder: coder) - _init() + public var body: some View { + HStack(spacing: 4) { + Image(uiImage: viewModel.icon) + .resizable() + .frame(width: imageDimension, height: imageDimension) + Text(viewModel.title) + } + .font(.callout) + .foregroundColor(.white) + .padding(.vertical, 8) } } extension ReplySettingBannerView { - private func _init() { - // Hack the background view to fill the table width - overflowBackgroundView.translatesAutoresizingMaskIntoConstraints = false - addSubview(overflowBackgroundView) - NSLayoutConstraint.activate([ - overflowBackgroundView.topAnchor.constraint(equalTo: topAnchor), - leadingAnchor.constraint(equalTo: overflowBackgroundView.leadingAnchor, constant: 400), - overflowBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 400), - overflowBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - - // Hack the line to fill the table width - topSeparator.translatesAutoresizingMaskIntoConstraints = false - addSubview(topSeparator) - NSLayoutConstraint.activate([ - topSeparator.topAnchor.constraint(equalTo: topSeparator.topAnchor), - leadingAnchor.constraint(equalTo: topSeparator.leadingAnchor, constant: 400), - topSeparator.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 400), - ]) + public class ViewModel: ObservableObject { + // input + public let replaySettings: Twitter.Entity.V2.Tweet.ReplySettings + public let authorUsername: String - // stackView: H - [ icon | label ] - stackView.axis = .horizontal - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.alignment = .center - stackView.spacing = 4 - addSubview(stackView) - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: topAnchor, constant: 8), - stackView.leadingAnchor.constraint(equalTo: leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor), - bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 8), - ]) + // output + public let icon: UIImage + public let title: String + public let shouldHidden: Bool - stackView.addArrangedSubview(imageView) - stackView.addArrangedSubview(label) - - overflowBackgroundView.backgroundColor = Asset.Colors.hightLight.color.withAlphaComponent(0.6) - imageView.tintColor = .white - label.textColor = .white + public init( + replaySettings: Twitter.Entity.V2.Tweet.ReplySettings, + authorUsername: String + ) { + self.replaySettings = replaySettings + self.authorUsername = authorUsername + self.icon = { + switch replaySettings { + case .everyone: + fallthrough + case .following: + return Asset.Communication.at.image.withRenderingMode(.alwaysTemplate) + case .mentionedUsers: + return Asset.Human.personCheckMini.image.withRenderingMode(.alwaysTemplate) + } + }() + self.title = { + switch replaySettings { + case .everyone: + return "" + case .following: + return L10n.Common.Controls.Status.ReplySettings.peopleUserFollowsOrMentionedCanReply("@\(authorUsername)") + case .mentionedUsers: + return L10n.Common.Controls.Status.ReplySettings.peopleUserMentionedCanReply("@\(authorUsername)") + } + }() + self.shouldHidden = replaySettings == .everyone + // end init + } } } + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +@available(iOS 13.0, *) +struct ReplySettingBannerView_Previews: PreviewProvider { + + static var previews: some View { + Group { + ReplySettingBannerView(viewModel: .init( + replaySettings: .following, + authorUsername: "alice" + )) + ReplySettingBannerView(viewModel: .init( + replaySettings: .mentionedUsers, + authorUsername: "alice" + )) + } + .background(Color.black) + } + +} + +#endif + From e8e7a9d17ebeeb90bf399073cec57da900484382 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 May 2023 20:53:37 +0800 Subject: [PATCH 7/7] chore: update version to 2.0.0 (129) --- NotificationService/Info.plist | 2 +- ShareExtension/Info.plist | 2 +- .../Control/ReplySettingBannerView.swift | 1 + TwidereX.xcodeproj/project.pbxproj | 36 +++++++++---------- TwidereX/Info.plist | 2 +- TwidereXIntent/Info.plist | 2 +- TwidereXTests/Info.plist | 2 +- TwidereXUITests/Info.plist | 2 +- 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index f9aa0a6a..4f6e87f4 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleVersion - 128 + 129 NSExtension NSExtensionPointIdentifier diff --git a/ShareExtension/Info.plist b/ShareExtension/Info.plist index 6758123f..8810f44f 100644 --- a/ShareExtension/Info.plist +++ b/ShareExtension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 2.0.0 CFBundleVersion - 128 + 129 NSExtension NSExtensionAttributes diff --git a/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift b/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift index aedfc955..b8b347ee 100644 --- a/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift +++ b/TwidereSDK/Sources/TwidereUI/Control/ReplySettingBannerView.swift @@ -6,6 +6,7 @@ // import UIKit +import SwiftUI import TwitterSDK import TwidereAsset import TwidereLocalization diff --git a/TwidereX.xcodeproj/project.pbxproj b/TwidereX.xcodeproj/project.pbxproj index 6839e4d6..9840a86c 100644 --- a/TwidereX.xcodeproj/project.pbxproj +++ b/TwidereX.xcodeproj/project.pbxproj @@ -3431,7 +3431,7 @@ CODE_SIGN_ENTITLEMENTS = TwidereX/TwidereX.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_ASSET_PATHS = "TwidereX/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereX/Info.plist; @@ -3458,7 +3458,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = Twidere; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -3481,7 +3481,7 @@ baseConfigurationReference = BC377CE93F0DE1E07208F697 /* Pods-TwidereX-TwidereXUITests.profile.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -3505,7 +3505,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; @@ -3532,7 +3532,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = TwidereXIntent/TwidereXIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXIntent/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TwidereXIntent; @@ -3561,7 +3561,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = NotificationService/Info.plist; @@ -3590,7 +3590,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = NotificationService/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Twidere. All rights reserved."; @@ -3618,7 +3618,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = NotificationService/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Twidere. All rights reserved."; @@ -3644,7 +3644,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; @@ -3671,7 +3671,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; @@ -3825,7 +3825,7 @@ CODE_SIGN_ENTITLEMENTS = TwidereX/TwidereX.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_ASSET_PATHS = "TwidereX/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereX/Info.plist; @@ -3857,7 +3857,7 @@ CODE_SIGN_ENTITLEMENTS = TwidereX/TwidereX.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_ASSET_PATHS = "TwidereX/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereX/Info.plist; @@ -3884,7 +3884,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = Twidere; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -3909,7 +3909,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = Twidere; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -3932,7 +3932,7 @@ baseConfigurationReference = 2E41729E598B3379FC09BC03 /* Pods-TwidereX-TwidereXUITests.debug.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -3955,7 +3955,7 @@ baseConfigurationReference = 44D01BA9E21B7F374DBDA38A /* Pods-TwidereX-TwidereXUITests.release.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -3979,7 +3979,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = TwidereXIntent/TwidereXIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXIntent/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TwidereXIntent; @@ -4006,7 +4006,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = TwidereXIntent/TwidereXIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 128; + CURRENT_PROJECT_VERSION = 129; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = TwidereXIntent/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TwidereXIntent; diff --git a/TwidereX/Info.plist b/TwidereX/Info.plist index 33f4967d..5f7a8ad6 100644 --- a/TwidereX/Info.plist +++ b/TwidereX/Info.plist @@ -40,7 +40,7 @@ CFBundleShortVersionString 2.0.0 CFBundleVersion - 128 + 129 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/TwidereXIntent/Info.plist b/TwidereXIntent/Info.plist index 57f097f7..14cabbb5 100644 --- a/TwidereXIntent/Info.plist +++ b/TwidereXIntent/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 2.0.0 CFBundleVersion - 128 + 129 NSExtension NSExtensionAttributes diff --git a/TwidereXTests/Info.plist b/TwidereXTests/Info.plist index e0d1d83e..f33f97dc 100644 --- a/TwidereXTests/Info.plist +++ b/TwidereXTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 2.0.0 CFBundleVersion - 128 + 129 diff --git a/TwidereXUITests/Info.plist b/TwidereXUITests/Info.plist index e0d1d83e..f33f97dc 100644 --- a/TwidereXUITests/Info.plist +++ b/TwidereXUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 2.0.0 CFBundleVersion - 128 + 129