diff --git a/Localization/StringsConvertor/input/en_US/app.json b/Localization/StringsConvertor/input/en_US/app.json index 0c3f16c7c..680d2cb32 100644 --- a/Localization/StringsConvertor/input/en_US/app.json +++ b/Localization/StringsConvertor/input/en_US/app.json @@ -19,6 +19,9 @@ "preview": "Preview", "open_in_safari": "Open in Safari" }, + "status": { + "userBoosted": "%s boosted" + }, "timeline": { "load_more": "Load More" } @@ -75,4 +78,4 @@ "title": "Public" } } -} \ No newline at end of file +} diff --git a/Localization/StringsConvertor/output/en.lproj/Localizable.strings b/Localization/StringsConvertor/output/en.lproj/Localizable.strings index 707ef3cce..92264accf 100644 --- a/Localization/StringsConvertor/output/en.lproj/Localizable.strings +++ b/Localization/StringsConvertor/output/en.lproj/Localizable.strings @@ -13,6 +13,7 @@ "Common.Controls.Actions.SignIn" = "Sign in"; "Common.Controls.Actions.SignUp" = "Sign up"; "Common.Controls.Actions.TakePhoto" = "Take photo"; +"Common.Controls.Status.Userboosted" = "%@ boosted"; "Common.Controls.Timeline.LoadMore" = "Load More"; "Common.Countable.Photo.Multiple" = "photos"; "Common.Countable.Photo.Single" = "photo"; diff --git a/Localization/app.json b/Localization/app.json index 0c3f16c7c..680d2cb32 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -19,6 +19,9 @@ "preview": "Preview", "open_in_safari": "Open in Safari" }, + "status": { + "userBoosted": "%s boosted" + }, "timeline": { "load_more": "Load More" } @@ -75,4 +78,4 @@ "title": "Public" } } -} \ No newline at end of file +} diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index c7180d3bf..d38924ff0 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; }; 2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; }; - 2D152A8C25C295CC009AA50C /* TimelinePostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* TimelinePostView.swift */; }; + 2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* StatusView.swift */; }; 2D152A9225C2980C009AA50C /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A9125C2980C009AA50C /* UIFont.swift */; }; 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; }; 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; }; @@ -33,7 +33,6 @@ 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */; }; 2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46976325C2A71500CF4AA9 /* UIIamge.swift */; }; 2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */; }; - 2D5A3D1125CF87AA002347D6 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D1025CF87AA002347D6 /* AvatarBarButtonItem.swift */; }; 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */; }; 2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */; }; 2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */; }; @@ -47,7 +46,7 @@ 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */; }; 2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */; }; 2D76319F25C1521200929FB9 /* TimelineSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* TimelineSection.swift */; }; - 2D7631A825C1535600929FB9 /* TimelinePostTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* TimelinePostTableViewCell.swift */; }; + 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; }; 2D7631B325C159F700929FB9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631B225C159F700929FB9 /* Item.swift */; }; 2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0125C7E4F2004F19B8 /* Mention.swift */; }; 2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0725C7E9A8004F19B8 /* Tag.swift */; }; @@ -74,6 +73,8 @@ DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; DB084B5725CBC56C00F898ED /* Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Toot.swift */; }; DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; }; + DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; }; + DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; }; DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; }; DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; }; DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; @@ -188,7 +189,7 @@ /* Begin PBXFileReference section */ 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = ""; }; - 2D152A8B25C295CC009AA50C /* TimelinePostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePostView.swift; sourceTree = ""; }; + 2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; }; 2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = ""; }; 2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; @@ -211,7 +212,6 @@ 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = ""; }; 2D46976325C2A71500CF4AA9 /* UIIamge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIIamge.swift; sourceTree = ""; }; 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlContainableScrollViews.swift; sourceTree = ""; }; - 2D5A3D1025CF87AA002347D6 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = ""; }; 2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+Diffable.swift"; sourceTree = ""; }; 2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewContainer.swift; sourceTree = ""; }; 2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+DebugAction.swift"; sourceTree = ""; }; @@ -224,7 +224,7 @@ 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+StatusProvider.swift"; sourceTree = ""; }; 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+Diffable.swift"; sourceTree = ""; }; 2D76319E25C1521200929FB9 /* TimelineSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSection.swift; sourceTree = ""; }; - 2D7631A725C1535600929FB9 /* TimelinePostTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePostTableViewCell.swift; sourceTree = ""; }; + 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = ""; }; 2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; 2D927F0125C7E4F2004F19B8 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = ""; }; 2D927F0725C7E9A8004F19B8 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = ""; }; @@ -256,6 +256,8 @@ DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Toot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toot.swift; sourceTree = ""; }; DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; + DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = ""; }; DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; @@ -393,7 +395,7 @@ 2D152A8A25C295B8009AA50C /* Content */ = { isa = PBXGroup; children = ( - 2D152A8B25C295CC009AA50C /* TimelinePostView.swift */, + 2D152A8B25C295CC009AA50C /* StatusView.swift */, ); path = Content; sourceTree = ""; @@ -436,7 +438,7 @@ children = ( DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */, 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */, - 2D5A3D1025CF87AA002347D6 /* AvatarBarButtonItem.swift */, + DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */, ); path = Button; sourceTree = ""; @@ -533,7 +535,7 @@ 2D7631A625C1533800929FB9 /* TableviewCell */ = { isa = PBXGroup; children = ( - 2D7631A725C1535600929FB9 /* TimelinePostTableViewCell.swift */, + 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */, 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */, 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */, 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */, @@ -614,6 +616,7 @@ isa = PBXGroup; children = ( DB427DDE25BAA00100D1B89D /* Assets.xcassets */, + DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */, DB3D100F25BAA75E00EAA174 /* Localizable.strings */, DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */, ); @@ -1069,6 +1072,7 @@ DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */, DB427DDF25BAA00100D1B89D /* Assets.xcassets in Resources */, DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */, + DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */, DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1238,7 +1242,7 @@ DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */, 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, - 2D152A8C25C295CC009AA50C /* TimelinePostView.swift in Sources */, + 2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */, 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */, DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */, 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */, @@ -1279,6 +1283,7 @@ 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, 2D76319F25C1521200929FB9 /* TimelineSection.swift in Sources */, + DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */, DB084B5725CBC56C00F898ED /* Toot.swift in Sources */, DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */, DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */, @@ -1300,7 +1305,7 @@ DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */, 2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */, 2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */, - 2D7631A825C1535600929FB9 /* TimelinePostTableViewCell.swift in Sources */, + 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */, 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */, 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */, DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */, @@ -1310,7 +1315,6 @@ 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */, 2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */, 2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */, - 2D5A3D1125CF87AA002347D6 /* AvatarBarButtonItem.swift in Sources */, DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Mastodon/Diffiable/Section/TimelineSection.swift b/Mastodon/Diffiable/Section/TimelineSection.swift index c9adbd833..97ca559f6 100644 --- a/Mastodon/Diffiable/Section/TimelineSection.swift +++ b/Mastodon/Diffiable/Section/TimelineSection.swift @@ -21,7 +21,7 @@ extension TimelineSection { dependency: NeedsDependency, managedObjectContext: NSManagedObjectContext, timestampUpdatePublisher: AnyPublisher, - timelinePostTableViewCellDelegate: TimelinePostTableViewCellDelegate, + timelinePostTableViewCellDelegate: StatusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate? ) -> UITableViewDiffableDataSource { UITableViewDiffableDataSource(tableView: tableView) { [weak timelinePostTableViewCellDelegate, weak timelineMiddleLoaderTableViewCellDelegate] tableView, indexPath, item -> UITableViewCell? in @@ -29,7 +29,7 @@ extension TimelineSection { switch item { case .homeTimelineIndex(objectID: let objectID, attribute: _): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelinePostTableViewCell.self), for: indexPath) as! TimelinePostTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell // configure cell managedObjectContext.performAndWait { @@ -39,7 +39,7 @@ extension TimelineSection { cell.delegate = timelinePostTableViewCellDelegate return cell case .toot(let objectID): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelinePostTableViewCell.self), for: indexPath) as! TimelinePostTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value let requestUserID = activeMastodonAuthenticationBox?.userID ?? "" // configure cell @@ -68,21 +68,22 @@ extension TimelineSection { } static func configure( - cell: TimelinePostTableViewCell, + cell: StatusTableViewCell, timestampUpdatePublisher: AnyPublisher, toot: Toot, requestUserID: String ) { + // set header + cell.statusView.headerContainerStackView.isHidden = toot.reblog == nil + cell.statusView.headerInfoLabel.text = L10n.Common.Controls.Status.userboosted(toot.author.displayName) + // set name username avatar - cell.timelinePostView.nameLabel.text = toot.author.displayName - cell.timelinePostView.usernameLabel.text = "@" + toot.author.username - cell.timelinePostView.avatarImageView.af.setImage( - withURL: URL(string: toot.author.avatar)!, - placeholderImage: UIImage.placeholder(color: .systemFill), - imageTransition: .crossDissolve(0.2) - ) + cell.statusView.nameLabel.text = toot.author.displayName + cell.statusView.usernameLabel.text = "@" + toot.author.username + cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: toot.author.avatarImageURL())) + // set text - cell.timelinePostView.activeTextLabel.config(content: toot.content) + cell.statusView.activeTextLabel.config(content: (toot.reblog ?? toot).content) // toolbar let isLike = (toot.reblog ?? toot).favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false @@ -90,15 +91,15 @@ extension TimelineSection { let count = (toot.reblog ?? toot).favouritesCount.intValue return TimelineSection.formattedNumberTitleForActionButton(count) }() - cell.timelinePostView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal) - cell.timelinePostView.actionToolbarContainer.isStarButtonHighlight = isLike + cell.statusView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal) + cell.statusView.actionToolbarContainer.isStarButtonHighlight = isLike // set date let createdAt = (toot.reblog ?? toot).createdAt - cell.timelinePostView.dateLabel.text = createdAt.shortTimeAgoSinceNow + cell.statusView.dateLabel.text = createdAt.shortTimeAgoSinceNow timestampUpdatePublisher .sink { _ in - cell.timelinePostView.dateLabel.text = createdAt.shortTimeAgoSinceNow + cell.statusView.dateLabel.text = createdAt.shortTimeAgoSinceNow } .store(in: &cell.disposeBag) @@ -115,8 +116,8 @@ extension TimelineSection { let isLike = targetToot.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false let favoriteCount = targetToot.favouritesCount.intValue let favoriteCountTitle = TimelineSection.formattedNumberTitleForActionButton(favoriteCount) - cell.timelinePostView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal) - cell.timelinePostView.actionToolbarContainer.isStarButtonHighlight = isLike + cell.statusView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal) + cell.statusView.actionToolbarContainer.isStarButtonHighlight = isLike os_log("%{public}s[%{public}ld], %{public}s: like count label for toot %s did update: %ld", (#file as NSString).lastPathComponent, #line, #function, targetToot.id, favoriteCount) } .store(in: &cell.disposeBag) diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift index 6e08d7268..539be0189 100644 --- a/Mastodon/Extension/ActiveLabel.swift +++ b/Mastodon/Extension/ActiveLabel.swift @@ -22,18 +22,18 @@ extension ActiveLabel { switch style { case .default: -// urlMaximumLength = 30 font = .preferredFont(forTextStyle: .body) - textColor = .white + textColor = Asset.Colors.Label.primary.color case .timelineHeaderView: font = .preferredFont(forTextStyle: .footnote) textColor = .secondaryLabel } numberOfLines = 0 - mentionColor = UIColor.yellow - hashtagColor = UIColor.blue - URLColor = UIColor.red + lineSpacing = 5 + mentionColor = Asset.Colors.Label.highlight.color + hashtagColor = Asset.Colors.Label.highlight.color + URLColor = Asset.Colors.Label.highlight.color text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." } @@ -43,12 +43,6 @@ extension ActiveLabel { func config(content: String) { if let parseResult = try? TootContent.parse(toot: content) { activeEntities.removeAll() - numberOfLines = 0 - font = UIFont(name: "SFProText-Regular", size: 16) - textColor = .white - URLColor = .systemRed - mentionColor = .systemGreen - hashtagColor = .systemBlue text = parseResult.trimmed activeEntities = parseResult.activeEntities } diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift index 18f25f927..dc3e8cf9d 100644 --- a/Mastodon/Generated/Assets.swift +++ b/Mastodon/Generated/Assets.swift @@ -28,8 +28,10 @@ internal enum Asset { internal enum Colors { internal enum Background { internal static let onboardingBackground = ColorAsset(name: "Colors/Background/onboarding.background") + internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Colors/Background/secondary.grouped.system.background") internal static let secondarySystemBackground = ColorAsset(name: "Colors/Background/secondary.system.background") internal static let systemBackground = ColorAsset(name: "Colors/Background/system.background") + internal static let systemGroupedBackground = ColorAsset(name: "Colors/Background/system.grouped.background") internal static let tertiarySystemBackground = ColorAsset(name: "Colors/Background/tertiary.system.background") } internal enum Button { @@ -40,7 +42,7 @@ internal enum Asset { internal static let plus = ColorAsset(name: "Colors/Icon/plus") } internal enum Label { - internal static let black = ColorAsset(name: "Colors/Label/black") + internal static let highlight = ColorAsset(name: "Colors/Label/highlight") internal static let primary = ColorAsset(name: "Colors/Label/primary") internal static let secondary = ColorAsset(name: "Colors/Label/secondary") } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index f8383a825..716baa2e6 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -45,6 +45,12 @@ internal enum L10n { /// Take photo internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto") } + internal enum Status { + /// %@ boosted + internal static func userboosted(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Status.Userboosted", String(describing: p1)) + } + } internal enum Timeline { /// Load More internal static let loadMore = L10n.tr("Localizable", "Common.Controls.Timeline.LoadMore") diff --git a/Mastodon/Protocol/AvatarConfigurableView.swift b/Mastodon/Protocol/AvatarConfigurableView.swift index dfeb3e5bb..6c51d576c 100644 --- a/Mastodon/Protocol/AvatarConfigurableView.swift +++ b/Mastodon/Protocol/AvatarConfigurableView.swift @@ -10,28 +10,19 @@ import AlamofireImage import Kingfisher protocol AvatarConfigurableView { - static var configurableAvatarImageViewSize: CGSize { get } - static var configurableAvatarImageViewBadgeAppearanceStyle: AvatarConfigurableViewConfiguration.BadgeAppearanceStyle { get } + static var configurableAvatarImageSize: CGSize { get } + static var configurableAvatarImageCornerRadius: CGFloat { get } var configurableAvatarImageView: UIImageView? { get } var configurableAvatarButton: UIButton? { get } - var configurableVerifiedBadgeImageView: UIImageView? { get } - func configure(withConfigurationInput input: AvatarConfigurableViewConfiguration.Input) + func configure(with configuration: AvatarConfigurableViewConfiguration) func avatarConfigurableView(_ avatarConfigurableView: AvatarConfigurableView, didFinishConfiguration configuration: AvatarConfigurableViewConfiguration) } extension AvatarConfigurableView { - static var configurableAvatarImageViewBadgeAppearanceStyle: AvatarConfigurableViewConfiguration.BadgeAppearanceStyle { return .mini } - - public func configure(withConfigurationInput input: AvatarConfigurableViewConfiguration.Input) { - // TODO: set badge - configurableVerifiedBadgeImageView?.isHidden = true - - let cornerRadius = Self.configurableAvatarImageViewSize.width * 0.5 - // let scale = (configurableAvatarImageView ?? configurableAvatarButton)?.window?.screen.scale ?? UIScreen.main.scale - + public func configure(with configuration: AvatarConfigurableViewConfiguration) { let placeholderImage: UIImage = { - let placeholderImage = input.placeholderImage ?? UIImage.placeholder(size: Self.configurableAvatarImageViewSize, color: .systemFill) + let placeholderImage = configuration.placeholderImage ?? UIImage.placeholder(size: Self.configurableAvatarImageSize, color: .systemFill) return placeholderImage.af.imageRoundedIntoCircle() }() @@ -51,12 +42,11 @@ extension AvatarConfigurableView { configurableAvatarButton?.layer.cornerCurve = .circular defer { - let configuration = AvatarConfigurableViewConfiguration(input: input) avatarConfigurableView(self, didFinishConfiguration: configuration) } // set placeholder if no asset - guard let avatarImageURL = input.avatarImageURL else { + guard let avatarImageURL = configuration.avatarImageURL else { configurableAvatarImageView?.image = placeholderImage configurableAvatarButton?.setImage(placeholderImage, for: .normal) return @@ -74,10 +64,10 @@ extension AvatarConfigurableView { ] ) avatarImageView.layer.masksToBounds = true - avatarImageView.layer.cornerRadius = cornerRadius + avatarImageView.layer.cornerRadius = Self.configurableAvatarImageCornerRadius avatarImageView.layer.cornerCurve = .circular default: - let filter = ScaledToSizeCircleFilter(size: Self.configurableAvatarImageViewSize) + let filter = ScaledToSizeWithRoundedCornersFilter(size: Self.configurableAvatarImageSize, radius: Self.configurableAvatarImageCornerRadius) avatarImageView.af.setImage( withURL: avatarImageURL, placeholderImage: placeholderImage, @@ -101,10 +91,10 @@ extension AvatarConfigurableView { ] ) avatarButton.layer.masksToBounds = true - avatarButton.layer.cornerRadius = cornerRadius - avatarButton.layer.cornerCurve = .circular + avatarButton.layer.cornerRadius = Self.configurableAvatarImageCornerRadius + avatarButton.layer.cornerCurve = .continuous default: - let filter = ScaledToSizeCircleFilter(size: Self.configurableAvatarImageViewSize) + let filter = ScaledToSizeWithRoundedCornersFilter(size: Self.configurableAvatarImageSize, radius: Self.configurableAvatarImageCornerRadius) avatarButton.af.setImage( for: .normal, url: avatarImageURL, @@ -122,25 +112,12 @@ extension AvatarConfigurableView { struct AvatarConfigurableViewConfiguration { - enum BadgeAppearanceStyle { - case mini - case normal - } + let avatarImageURL: URL? + let placeholderImage: UIImage? - 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 - } + init(avatarImageURL: URL?, placeholderImage: UIImage? = nil) { + self.avatarImageURL = avatarImageURL + self.placeholderImage = placeholderImage } - let input: Input - } diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift index 9b50071f7..1850f7f5e 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift @@ -14,9 +14,9 @@ import MastodonSDK import ActiveLabel // MARK: - ActionToolbarContainerDelegate -extension TimelinePostTableViewCellDelegate where Self: StatusProvider { +extension StatusTableViewCellDelegate where Self: StatusProvider { - func timelinePostTableViewCell(_ cell: TimelinePostTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) { + func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) { StatusProviderFacade.responseToStatusLikeAction(provider: self, cell: cell) } diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json new file mode 100644 index 000000000..abe46b9aa --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "255", + "green" : "255", + "red" : "255" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x37", + "green" : "0x2D", + "red" : "0x29" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json new file mode 100644 index 000000000..edc0dce9a --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "232", + "green" : "225", + "red" : "217" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.169", + "green" : "0.141", + "red" : "0.125" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/black.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/highlight.colorset/Contents.json similarity index 74% rename from Mastodon/Resources/Assets.xcassets/Colors/Label/black.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/Colors/Label/highlight.colorset/Contents.json index 95a50e5d3..2e1ce5f3a 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Label/black.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/highlight.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0", - "green" : "0", - "red" : "0" + "blue" : "0.851", + "green" : "0.565", + "red" : "0.169" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json index fafa47672..202a1c04e 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/primary.colorset/Contents.json @@ -5,9 +5,27 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0xFF", - "red" : "0xFF" + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json index a47dfc697..2c20abe7c 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json @@ -4,10 +4,28 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.000", - "blue" : "132", - "green" : "105", - "red" : "96" + "alpha" : "0.600", + "blue" : "0x43", + "green" : "0x3C", + "red" : "0x3C" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "0x43", + "green" : "0x3C", + "red" : "0x3C" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/lightBrandBlue.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/lightBrandBlue.colorset/Contents.json index 2e1ce5f3a..d853a71aa 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/lightBrandBlue.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/lightBrandBlue.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.851", - "green" : "0.565", - "red" : "0.169" + "blue" : "217", + "green" : "144", + "red" : "43" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Preview Assets.xcassets/Contents.json b/Mastodon/Resources/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Mastodon/Resources/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Preview Assets.xcassets/tiraya-adam-QfHEWqPelsc-unsplash.imageset/Contents.json b/Mastodon/Resources/Preview Assets.xcassets/tiraya-adam-QfHEWqPelsc-unsplash.imageset/Contents.json new file mode 100644 index 000000000..54d7079e8 --- /dev/null +++ b/Mastodon/Resources/Preview Assets.xcassets/tiraya-adam-QfHEWqPelsc-unsplash.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tiraya-adam-QfHEWqPelsc-unsplash.jpg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Preview Assets.xcassets/tiraya-adam-QfHEWqPelsc-unsplash.imageset/tiraya-adam-QfHEWqPelsc-unsplash.jpg b/Mastodon/Resources/Preview Assets.xcassets/tiraya-adam-QfHEWqPelsc-unsplash.imageset/tiraya-adam-QfHEWqPelsc-unsplash.jpg new file mode 100644 index 000000000..3670c92ae Binary files /dev/null and b/Mastodon/Resources/Preview Assets.xcassets/tiraya-adam-QfHEWqPelsc-unsplash.imageset/tiraya-adam-QfHEWqPelsc-unsplash.jpg differ diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 707ef3cce..92264accf 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -13,6 +13,7 @@ "Common.Controls.Actions.SignIn" = "Sign in"; "Common.Controls.Actions.SignUp" = "Sign up"; "Common.Controls.Actions.TakePhoto" = "Take photo"; +"Common.Controls.Status.Userboosted" = "%@ boosted"; "Common.Controls.Timeline.LoadMore" = "Load More"; "Common.Countable.Photo.Multiple" = "photos"; "Common.Countable.Photo.Single" = "photo"; diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift index 98f876c3d..1ebb17bd5 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift @@ -39,7 +39,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { let largeTitleLabel: UILabel = { let label = UILabel() label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.boldSystemFont(ofSize: 34)) - label.textColor = Asset.Colors.Label.black.color + label.textColor = .black label.text = L10n.Scene.Register.title return label }() @@ -87,7 +87,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { let domainLabel: UILabel = { let label = UILabel() label.font = .preferredFont(forTextStyle: .headline) - label.textColor = Asset.Colors.Label.black.color + label.textColor = .black return label }() diff --git a/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift b/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift index affd69d4d..826a8731a 100644 --- a/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift +++ b/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift @@ -35,7 +35,7 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency let rulesLabel: UILabel = { let label = UILabel() label.font = .preferredFont(forTextStyle: .body) - label.textColor = Asset.Colors.Label.black.color + label.textColor = .black label.text = "Rules" label.numberOfLines = 0 return label diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index cbd3fa9b9..32eb29d58 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -15,7 +15,7 @@ import GameplayKit import MastodonSDK import AlamofireImage -final class HomeTimelineViewController: UIViewController, NeedsDependency,TimelinePostTableViewCellDelegate { +final class HomeTimelineViewController: UIViewController, NeedsDependency,StatusTableViewCellDelegate { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -27,7 +27,7 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency,Timeli let tableView: UITableView = { let tableView = ControlContainableTableView() - tableView.register(TimelinePostTableViewCell.self, forCellReuseIdentifier: String(describing: TimelinePostTableViewCell.self)) + tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self)) tableView.register(TimelineMiddleLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineMiddleLoaderTableViewCell.self)) tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) tableView.rowHeight = UITableView.automaticDimension @@ -51,7 +51,7 @@ extension HomeTimelineViewController { super.viewDidLoad() title = L10n.Scene.HomeTimeline.title - view.backgroundColor = Asset.Colors.Background.systemBackground.color + view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color navigationItem.leftBarButtonItem = avatarBarButtonItem avatarBarButtonItem.avatarButton.addTarget(self, action: #selector(HomeTimelineViewController.avatarButtonPressed(_:)), for: .touchUpInside) @@ -105,12 +105,10 @@ extension HomeTimelineViewController { guard let self = self else { return } guard let user = activeMastodonAuthentication?.user, let avatarImageURL = user.avatarImageURL() else { - let input = AvatarConfigurableViewConfiguration.Input(avatarImageURL: nil) - self.avatarBarButtonItem.configure(withConfigurationInput: input) + self.avatarBarButtonItem.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: nil)) return } - let input = AvatarConfigurableViewConfiguration.Input(avatarImageURL: avatarImageURL) - self.avatarBarButtonItem.configure(withConfigurationInput: input) + self.avatarBarButtonItem.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: avatarImageURL)) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift index 5a63fe26a..d37d5e12c 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift @@ -15,7 +15,7 @@ extension HomeTimelineViewModel { func setupDiffableDataSource( for tableView: UITableView, dependency: NeedsDependency, - timelinePostTableViewCellDelegate: TimelinePostTableViewCellDelegate, + timelinePostTableViewCellDelegate: StatusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate ) { let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common) diff --git a/Mastodon/Scene/PublicTimeline/PublicTimelineViewController.swift b/Mastodon/Scene/PublicTimeline/PublicTimelineViewController.swift index fd72a18c1..62993bfb5 100644 --- a/Mastodon/Scene/PublicTimeline/PublicTimelineViewController.swift +++ b/Mastodon/Scene/PublicTimeline/PublicTimelineViewController.swift @@ -13,7 +13,7 @@ import GameplayKit import os.log import UIKit -final class PublicTimelineViewController: UIViewController, NeedsDependency, TimelinePostTableViewCellDelegate { +final class PublicTimelineViewController: UIViewController, NeedsDependency, StatusTableViewCellDelegate { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -24,7 +24,7 @@ final class PublicTimelineViewController: UIViewController, NeedsDependency, Tim lazy var tableView: UITableView = { let tableView = UITableView() - tableView.register(TimelinePostTableViewCell.self, forCellReuseIdentifier: String(describing: TimelinePostTableViewCell.self)) + tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self)) tableView.register(TimelineMiddleLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineMiddleLoaderTableViewCell.self)) tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) tableView.rowHeight = UITableView.automaticDimension @@ -42,7 +42,7 @@ extension PublicTimelineViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = Asset.Colors.Background.systemBackground.color + view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color tableView.refreshControl = refreshControl refreshControl.addTarget(self, action: #selector(PublicTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged) diff --git a/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel+Diffable.swift b/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel+Diffable.swift index b3e8b27f6..0e235b7ab 100644 --- a/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel+Diffable.swift @@ -14,7 +14,7 @@ extension PublicTimelineViewModel { func setupDiffableDataSource( for tableView: UITableView, dependency: NeedsDependency, - timelinePostTableViewCellDelegate: TimelinePostTableViewCellDelegate, + timelinePostTableViewCellDelegate: StatusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate ) { let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common) diff --git a/Mastodon/Scene/Share/View/Button/AvatarBarButtonItem.swift b/Mastodon/Scene/Share/View/Button/AvatarBarButtonItem.swift index f117c2120..254403bde 100644 --- a/Mastodon/Scene/Share/View/Button/AvatarBarButtonItem.swift +++ b/Mastodon/Scene/Share/View/Button/AvatarBarButtonItem.swift @@ -42,7 +42,8 @@ extension AvatarBarButtonItem { } extension AvatarBarButtonItem: AvatarConfigurableView { - static var configurableAvatarImageViewSize: CGSize { return avatarButtonSize } + static var configurableAvatarImageSize: CGSize { return avatarButtonSize } + static var configurableAvatarImageCornerRadius: CGFloat { return 4 } var configurableAvatarImageView: UIImageView? { return nil } var configurableAvatarButton: UIButton? { return avatarButton } var configurableVerifiedBadgeImageView: UIImageView? { return nil } diff --git a/Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift b/Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift new file mode 100644 index 000000000..3eb916f26 --- /dev/null +++ b/Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift @@ -0,0 +1,35 @@ +// +// HighlightDimmableButton.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-2-23. +// + +import UIKit + +final class HighlightDimmableButton: UIButton { + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + + + override var isHighlighted: Bool { + didSet { + alpha = isHighlighted ? 0.6 : 1 + } + } + +} + +extension HighlightDimmableButton { + private func _init() { + adjustsImageWhenHighlighted = false + } +} diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift new file mode 100644 index 000000000..af154db6e --- /dev/null +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -0,0 +1,257 @@ +// +// StatusView.swift +// Mastodon +// +// Created by sxiaojian on 2021/1/28. +// + +import UIKit +import AVKit +import ActiveLabel +import AlamofireImage + +final class StatusView: UIView { + + static let avatarImageSize = CGSize(width: 42, height: 42) + static let avatarImageCornerRadius: CGFloat = 4 + + let headerContainerStackView = UIStackView() + + let headerIconLabel: UILabel = { + let label = UILabel() + let attributedString = NSMutableAttributedString() + let imageTextAttachment = NSTextAttachment() + let font = UIFont.systemFont(ofSize: 13, weight: .medium) + let configuration = UIImage.SymbolConfiguration(font: font) + imageTextAttachment.image = UIImage(systemName: "arrow.2.squarepath", withConfiguration: configuration)?.withTintColor(Asset.Colors.Label.secondary.color) + let imageAttribute = NSAttributedString(attachment: imageTextAttachment) + attributedString.append(imageAttribute) + label.attributedText = attributedString + return label + }() + + let headerInfoLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium)) + label.textColor = Asset.Colors.Label.secondary.color + label.text = "Bob boosted" + return label + }() + + let avatarView = UIView() + let avatarButton: UIButton = { + let button = HighlightDimmableButton(type: .custom) + let placeholderImage = UIImage.placeholder(size: avatarImageSize, color: .systemFill) + .af.imageRounded(withCornerRadius: StatusView.avatarImageCornerRadius, divideRadiusByImageScale: true) + button.setImage(placeholderImage, for: .normal) + return button + }() + + let visibilityImageView: UIImageView = { + let imageView = UIImageView(image: Asset.TootTimeline.global.image.withRenderingMode(.alwaysTemplate)) + imageView.tintColor = Asset.Colors.Label.secondary.color + return imageView + }() + + let lockImageView: UIImageView = { + let imageview = UIImageView(image: Asset.TootTimeline.textlock.image.withRenderingMode(.alwaysTemplate)) + imageview.tintColor = Asset.Colors.Label.secondary.color + imageview.isHidden = true + return imageview + }() + + let nameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 17, weight: .semibold) + label.textColor = Asset.Colors.Label.primary.color + label.text = "Alice" + return label + }() + + let usernameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15, weight: .regular) + label.textColor = Asset.Colors.Label.secondary.color + label.text = "@alice" + return label + }() + + let dateLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 13, weight: .regular) + label.textColor = Asset.Colors.Label.secondary.color + label.text = "1d" + return label + }() + + let statusContainerStackView = UIStackView() + + let actionToolbarContainer: ActionToolbarContainer = { + let actionToolbarContainer = ActionToolbarContainer() + actionToolbarContainer.configure(for: .inline) + return actionToolbarContainer + }() + + + let activeTextLabel = ActiveLabel(style: .default) + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension StatusView { + + func _init() { + // container: [retoot | author | status | action toolbar] + let containerStackView = UIStackView() + containerStackView.axis = .vertical + containerStackView.spacing = 10 + containerStackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(containerStackView) + NSLayoutConstraint.activate([ + containerStackView.topAnchor.constraint(equalTo: topAnchor), + containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor), + bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor), + ]) + + // header container: [icon | info] + containerStackView.addArrangedSubview(headerContainerStackView) + headerContainerStackView.spacing = 4 + headerContainerStackView.addArrangedSubview(headerIconLabel) + headerContainerStackView.addArrangedSubview(headerInfoLabel) + headerIconLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + // author container: [avatar | author meta container] + let authorContainerStackView = UIStackView() + containerStackView.addArrangedSubview(authorContainerStackView) + authorContainerStackView.axis = .horizontal + authorContainerStackView.spacing = 5 + + // avatar + avatarView.translatesAutoresizingMaskIntoConstraints = false + authorContainerStackView.addArrangedSubview(avatarView) + NSLayoutConstraint.activate([ + avatarView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.required - 1), + avatarView.heightAnchor.constraint(equalToConstant: StatusView.avatarImageSize.height).priority(.required - 1), + ]) + avatarButton.translatesAutoresizingMaskIntoConstraints = false + avatarView.addSubview(avatarButton) + NSLayoutConstraint.activate([ + avatarButton.topAnchor.constraint(equalTo: avatarView.topAnchor), + avatarButton.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor), + avatarButton.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), + avatarButton.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), + ]) + + // author meta container: [title container | subtitle container] + let authorMetaContainerStackView = UIStackView() + authorContainerStackView.addArrangedSubview(authorMetaContainerStackView) + authorMetaContainerStackView.axis = .vertical + authorMetaContainerStackView.spacing = 4 + + // title container: [display name | "·" | date] + let titleContainerStackView = UIStackView() + authorMetaContainerStackView.addArrangedSubview(titleContainerStackView) + titleContainerStackView.axis = .horizontal + titleContainerStackView.spacing = 4 + nameLabel.translatesAutoresizingMaskIntoConstraints = false + titleContainerStackView.addArrangedSubview(nameLabel) + NSLayoutConstraint.activate([ + nameLabel.heightAnchor.constraint(equalToConstant: 22).priority(.defaultHigh), + ]) + titleContainerStackView.alignment = .firstBaseline + let dotLabel: UILabel = { + let label = UILabel() + label.textColor = Asset.Colors.Label.secondary.color + label.font = .systemFont(ofSize: 17) + label.text = "·" + return label + }() + titleContainerStackView.addArrangedSubview(dotLabel) + titleContainerStackView.addArrangedSubview(dateLabel) + nameLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) + dotLabel.setContentHuggingPriority(.defaultHigh + 2, for: .horizontal) + dotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal) + dateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal) + + // subtitle container: [username] + let subtitleContainerStackView = UIStackView() + authorMetaContainerStackView.addArrangedSubview(subtitleContainerStackView) + subtitleContainerStackView.axis = .horizontal + subtitleContainerStackView.addArrangedSubview(usernameLabel) + + // status container: [status | image / video | audio] + containerStackView.addArrangedSubview(statusContainerStackView) + statusContainerStackView.axis = .vertical + statusContainerStackView.spacing = 10 + statusContainerStackView.addArrangedSubview(activeTextLabel) + activeTextLabel.setContentCompressionResistancePriority(.required - 2, for: .vertical) + + // action toolbar container + containerStackView.addArrangedSubview(actionToolbarContainer) + actionToolbarContainer.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + + headerContainerStackView.isHidden = true + } + +} + +extension StatusView: AvatarConfigurableView { + static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize } + static var configurableAvatarImageCornerRadius: CGFloat { return 4 } + var configurableAvatarImageView: UIImageView? { return nil } + var configurableAvatarButton: UIButton? { return avatarButton } + var configurableVerifiedBadgeImageView: UIImageView? { nil } + + +} + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +struct StatusView_Previews: PreviewProvider { + + static let avatarFlora = UIImage(named: "tiraya-adam-QfHEWqPelsc-unsplash") + + static var previews: some View { + Group { + UIViewPreview(width: 375) { + let statusView = StatusView() + statusView.configure( + with: AvatarConfigurableViewConfiguration( + avatarImageURL: nil, + placeholderImage: avatarFlora + ) + ) + return statusView + } + .previewLayout(.fixed(width: 375, height: 200)) + UIViewPreview(width: 375) { + let statusView = StatusView() + statusView.configure( + with: AvatarConfigurableViewConfiguration( + avatarImageURL: nil, + placeholderImage: avatarFlora + ) + ) + statusView.headerContainerStackView.isHidden = false + return statusView + } + .previewLayout(.fixed(width: 375, height: 200)) + } + } + +} + +#endif + diff --git a/Mastodon/Scene/Share/View/Content/TimelinePostView.swift b/Mastodon/Scene/Share/View/Content/TimelinePostView.swift deleted file mode 100644 index b1c652bf5..000000000 --- a/Mastodon/Scene/Share/View/Content/TimelinePostView.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// TimelinePostView.swift -// Mastodon -// -// Created by sxiaojian on 2021/1/28. -// - -import UIKit -import AVKit -import ActiveLabel - -final class TimelinePostView: UIView { - - static let avatarImageViewSize = CGSize(width: 44, height: 44) - - let avatarImageView: UIImageView = { - let imageView = UIImageView() - imageView.layer.masksToBounds = true - imageView.layer.cornerRadius = avatarImageViewSize.width/2 - imageView.layer.cornerCurve = .continuous - imageView.contentMode = .scaleAspectFill - return imageView - }() - - let visibilityImageView: UIImageView = { - let imageView = UIImageView(image: Asset.TootTimeline.global.image.withRenderingMode(.alwaysTemplate)) - imageView.tintColor = Asset.Colors.Label.secondary.color - return imageView - }() - - let lockImageView: UIImageView = { - let imageview = UIImageView(image: Asset.TootTimeline.textlock.image.withRenderingMode(.alwaysTemplate)) - imageview.tintColor = Asset.Colors.Label.secondary.color - imageview.isHidden = true - return imageview - }() - - let nameLabel: UILabel = { - let label = UILabel() - label.font = UIFont(name: "Roboto-Medium", size: 14) - label.textColor = Asset.Colors.Label.primary.color - - label.text = "Alice" - return label - }() - - let usernameLabel: UILabel = { - let label = UILabel() - label.textColor = Asset.Colors.Label.secondary.color - label.font = UIFont(name: "Roboto-Regular", size: 14) - label.text = "@alice" - return label - }() - - let dateLabel: UILabel = { - let label = UILabel() - label.font = UIFont(name: "Roboto-Regular", size: 14) - label.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? .left : .right - label.textColor = Asset.Colors.Label.secondary.color - label.text = "1d" - return label - }() - - let actionToolbarContainer: ActionToolbarContainer = { - let actionToolbarContainer = ActionToolbarContainer() - actionToolbarContainer.configure(for: .inline) - return actionToolbarContainer - }() - - let mainContainerStackView = UIStackView() - - let activeTextLabel = ActiveLabel(style: .default) - - override init(frame: CGRect) { - super.init(frame: frame) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - -} - -extension TimelinePostView { - - func _init() { - // container: [retoot | post] - let containerStackView = UIStackView() - containerStackView.axis = .vertical - containerStackView.spacing = 8 - //containerStackView.alignment = .top - containerStackView.translatesAutoresizingMaskIntoConstraints = false - addSubview(containerStackView) - NSLayoutConstraint.activate([ - containerStackView.topAnchor.constraint(equalTo: topAnchor), - containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), - trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor), - bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor), - ]) - - // post container: [user avatar | toot container] - let postContainerStackView = UIStackView() - containerStackView.addArrangedSubview(postContainerStackView) - postContainerStackView.axis = .horizontal - postContainerStackView.spacing = 10 - postContainerStackView.alignment = .top - - // user avatar - avatarImageView.translatesAutoresizingMaskIntoConstraints = false - postContainerStackView.addArrangedSubview(avatarImageView) - NSLayoutConstraint.activate([ - avatarImageView.widthAnchor.constraint(equalToConstant: TimelinePostView.avatarImageViewSize.width).priority(.required - 1), - avatarImageView.heightAnchor.constraint(equalToConstant: TimelinePostView.avatarImageViewSize.height).priority(.required - 1), - ]) - - // toot container: [user meta container | main container | action toolbar] - let tootContainerStackView = UIStackView() - postContainerStackView.addArrangedSubview(tootContainerStackView) - tootContainerStackView.axis = .vertical - tootContainerStackView.spacing = 2 - - // user meta container: [name | lock | username | visiablity | date ] - let userMetaContainerStackView = UIStackView() - tootContainerStackView.addArrangedSubview(userMetaContainerStackView) - userMetaContainerStackView.axis = .horizontal - userMetaContainerStackView.alignment = .center - userMetaContainerStackView.spacing = 6 - userMetaContainerStackView.addArrangedSubview(nameLabel) - userMetaContainerStackView.addArrangedSubview(lockImageView) - userMetaContainerStackView.addArrangedSubview(usernameLabel) - userMetaContainerStackView.addArrangedSubview(visibilityImageView) - userMetaContainerStackView.addArrangedSubview(dateLabel) - nameLabel.setContentHuggingPriority(.defaultHigh + 10, for: .horizontal) - nameLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - lockImageView.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal) - lockImageView.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) - usernameLabel.setContentHuggingPriority(.defaultHigh - 3, for: .horizontal) - usernameLabel.setContentCompressionResistancePriority(.defaultHigh - 1, for: .horizontal) - visibilityImageView.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal) - visibilityImageView.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) - dateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) - dateLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal) - - // main container: [text | image / video | quote | geo] - tootContainerStackView.addArrangedSubview(mainContainerStackView) - mainContainerStackView.axis = .vertical - mainContainerStackView.spacing = 8 - activeTextLabel.translatesAutoresizingMaskIntoConstraints = false - mainContainerStackView.addArrangedSubview(activeTextLabel) - - activeTextLabel.setContentCompressionResistancePriority(.required - 2, for: .vertical) - - // action toolbar - actionToolbarContainer.translatesAutoresizingMaskIntoConstraints = false - tootContainerStackView.addArrangedSubview(actionToolbarContainer) - actionToolbarContainer.setContentCompressionResistancePriority(.defaultLow, for: .vertical) - - } - -} - diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift new file mode 100644 index 000000000..7d429c765 --- /dev/null +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -0,0 +1,93 @@ +// +// StatusTableViewCell.swift +// Mastodon +// +// Created by sxiaojian on 2021/1/27. +// + +import os.log +import UIKit +import AVKit +import Combine + + +protocol StatusTableViewCellDelegate: class { + func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) +} + +final class StatusTableViewCell: UITableViewCell { + + + weak var delegate: StatusTableViewCellDelegate? + + var disposeBag = Set() + var observations = Set() + + let statusView = StatusView() + + override func prepareForReuse() { + super.prepareForReuse() + disposeBag.removeAll() + observations.removeAll() + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension StatusTableViewCell { + + private func _init() { + selectionStyle = .none + backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + + statusView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(statusView) + NSLayoutConstraint.activate([ + statusView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20), + statusView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), + contentView.readableContentGuide.trailingAnchor.constraint(equalTo: statusView.trailingAnchor), + ]) + + let bottomPaddingView = UIView() + bottomPaddingView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(bottomPaddingView) + NSLayoutConstraint.activate([ + bottomPaddingView.topAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 10), + bottomPaddingView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + bottomPaddingView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + bottomPaddingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + bottomPaddingView.heightAnchor.constraint(equalToConstant: 10).priority(.defaultHigh), + ]) + + statusView.actionToolbarContainer.delegate = self + bottomPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + } + +} +// MARK: - ActionToolbarContainerDelegate +extension StatusTableViewCell: ActionToolbarContainerDelegate { + func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) { + + } + func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, retootButtonDidPressed sender: UIButton) { + + } + func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) { + delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, likeButtonDidPressed: sender) + } + func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, bookmarkButtonDidPressed sender: UIButton) { + + } + func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) { + + } +} diff --git a/Mastodon/Scene/Share/View/TableviewCell/TimelinePostTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/TimelinePostTableViewCell.swift deleted file mode 100644 index 9a513dd79..000000000 --- a/Mastodon/Scene/Share/View/TableviewCell/TimelinePostTableViewCell.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// TimelinePostTableViewCell.swift -// Mastodon -// -// Created by sxiaojian on 2021/1/27. -// - -import os.log -import UIKit -import AVKit -import Combine - - -protocol TimelinePostTableViewCellDelegate: class { - func timelinePostTableViewCell(_ cell: TimelinePostTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) -} - -final class TimelinePostTableViewCell: UITableViewCell { - - static let verticalMargin: CGFloat = 16 // without retoot indicator - static let verticalMarginAlt: CGFloat = 8 // with retoot indicator - - weak var delegate: TimelinePostTableViewCellDelegate? - - var disposeBag = Set() - var observations = Set() - - let timelinePostView = TimelinePostView() - - var timelinePostViewTopLayoutConstraint: NSLayoutConstraint! - - override func prepareForReuse() { - super.prepareForReuse() - disposeBag.removeAll() - observations.removeAll() - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - -} - -extension TimelinePostTableViewCell { - - private func _init() { - self.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color - self.selectionStyle = .none - timelinePostView.translatesAutoresizingMaskIntoConstraints = false - timelinePostViewTopLayoutConstraint = timelinePostView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: TimelinePostTableViewCell.verticalMargin) - contentView.addSubview(timelinePostView) - NSLayoutConstraint.activate([ - timelinePostViewTopLayoutConstraint, - timelinePostView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), - contentView.readableContentGuide.trailingAnchor.constraint(equalTo: timelinePostView.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: timelinePostView.bottomAnchor), // use action toolbar margin - ]) - - timelinePostView.actionToolbarContainer.delegate = self - } - -} -// MARK: - ActionToolbarContainerDelegate -extension TimelinePostTableViewCell: ActionToolbarContainerDelegate { - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) { - - } - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, retootButtonDidPressed sender: UIButton) { - - } - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) { - delegate?.timelinePostTableViewCell(self, actionToolbarContainer: actionToolbarContainer, likeButtonDidPressed: sender) - } - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, bookmarkButtonDidPressed sender: UIButton) { - - } - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) { - - } -} diff --git a/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift b/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift index 4be522b41..314050612 100644 --- a/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift +++ b/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift @@ -12,7 +12,6 @@ protocol ActionToolbarContainerDelegate: class { func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, retootButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) - func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, bookmarkButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) } @@ -23,7 +22,6 @@ final class ActionToolbarContainer: UIView { let replyButton = HitTestExpandedButton() let retootButton = HitTestExpandedButton() let starButton = HitTestExpandedButton() - let bookmartButton = HitTestExpandedButton() let moreButton = HitTestExpandedButton() var isStarButtonHighlight: Bool = false { @@ -62,7 +60,6 @@ extension ActionToolbarContainer { replyButton.addTarget(self, action: #selector(ActionToolbarContainer.replyButtonDidPressed(_:)), for: .touchUpInside) retootButton.addTarget(self, action: #selector(ActionToolbarContainer.retootButtonDidPressed(_:)), for: .touchUpInside) starButton.addTarget(self, action: #selector(ActionToolbarContainer.starButtonDidPressed(_:)), for: .touchUpInside) - bookmartButton.addTarget(self, action: #selector(ActionToolbarContainer.bookmarkButtonDidPressed(_:)), for: .touchUpInside) moreButton.addTarget(self, action: #selector(ActionToolbarContainer.moreButtonDidPressed(_:)), for: .touchUpInside) } @@ -93,25 +90,29 @@ extension ActionToolbarContainer { subview.removeFromSuperview() } - let buttons = [replyButton, retootButton, starButton,bookmartButton, moreButton] + let buttons = [replyButton, retootButton, starButton, moreButton] buttons.forEach { button in - button.tintColor = Asset.Colors.Label.secondary.color + button.tintColor = UIColor.black.withAlphaComponent(0.6) button.titleLabel?.font = .monospacedDigitSystemFont(ofSize: 12, weight: .regular) button.setTitle("", for: .normal) button.setTitleColor(.secondaryLabel, for: .normal) button.setInsets(forContentPadding: .zero, imageTitlePadding: style.buttonTitleImagePadding) } + let replyImage = UIImage(systemName: "arrowshape.turn.up.left.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .ultraLight))!.withRenderingMode(.alwaysTemplate) + let reblogImage = UIImage(systemName: "arrow.2.squarepath", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .bold))!.withRenderingMode(.alwaysTemplate) + let starImage = UIImage(systemName: "star.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .bold))!.withRenderingMode(.alwaysTemplate) + let moreImage = UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .bold))!.withRenderingMode(.alwaysTemplate) + switch style { case .inline: buttons.forEach { button in button.contentHorizontalAlignment = .leading } - replyButton.setImage(Asset.ToolBar.reply.image.withRenderingMode(.alwaysTemplate), for: .normal) - retootButton.setImage(Asset.ToolBar.retoot.image.withRenderingMode(.alwaysTemplate), for: .normal) - starButton.setImage(Asset.ToolBar.star.image.withRenderingMode(.alwaysTemplate), for: .normal) - bookmartButton.setImage(Asset.ToolBar.bookmark.image.withRenderingMode(.alwaysTemplate), for: .normal) - moreButton.setImage(Asset.ToolBar.more.image.withRenderingMode(.alwaysTemplate), for: .normal) + replyButton.setImage(replyImage, for: .normal) + retootButton.setImage(reblogImage, for: .normal) + starButton.setImage(starImage, for: .normal) + moreButton.setImage(moreImage, for: .normal) container.axis = .horizontal container.distribution = .fill @@ -119,22 +120,18 @@ extension ActionToolbarContainer { replyButton.translatesAutoresizingMaskIntoConstraints = false retootButton.translatesAutoresizingMaskIntoConstraints = false starButton.translatesAutoresizingMaskIntoConstraints = false - bookmartButton.translatesAutoresizingMaskIntoConstraints = false moreButton.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(replyButton) container.addArrangedSubview(retootButton) container.addArrangedSubview(starButton) - container.addArrangedSubview(bookmartButton) container.addArrangedSubview(moreButton) NSLayoutConstraint.activate([ - replyButton.heightAnchor.constraint(equalToConstant: 40).priority(.defaultHigh), + replyButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: retootButton.heightAnchor).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: starButton.heightAnchor).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: moreButton.heightAnchor).priority(.defaultHigh), - replyButton.heightAnchor.constraint(equalTo: bookmartButton.heightAnchor).priority(.defaultHigh), replyButton.widthAnchor.constraint(equalTo: retootButton.widthAnchor).priority(.defaultHigh), replyButton.widthAnchor.constraint(equalTo: starButton.widthAnchor).priority(.defaultHigh), - replyButton.widthAnchor.constraint(equalTo: bookmartButton.widthAnchor).priority(.defaultHigh), ]) moreButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) moreButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) @@ -143,10 +140,9 @@ extension ActionToolbarContainer { buttons.forEach { button in button.contentHorizontalAlignment = .center } - replyButton.setImage(Asset.ToolBar.reply.image.withRenderingMode(.alwaysTemplate), for: .normal) - retootButton.setImage(Asset.ToolBar.retoot.image.withRenderingMode(.alwaysTemplate), for: .normal) - starButton.setImage(Asset.ToolBar.bookmark.image.withRenderingMode(.alwaysTemplate), for: .normal) - bookmartButton.setImage(Asset.ToolBar.bookmark.image.withRenderingMode(.alwaysTemplate), for: .normal) + replyButton.setImage(replyImage, for: .normal) + retootButton.setImage(reblogImage, for: .normal) + starButton.setImage(starImage, for: .normal) container.axis = .horizontal container.spacing = 8 @@ -155,7 +151,6 @@ extension ActionToolbarContainer { container.addArrangedSubview(replyButton) container.addArrangedSubview(retootButton) container.addArrangedSubview(starButton) - container.addArrangedSubview(bookmartButton) } } @@ -165,7 +160,7 @@ extension ActionToolbarContainer { } private func isStarButtonHighlightStateDidChange(to isHighlight: Bool) { - let tintColor = isHighlight ? Asset.Colors.systemOrange.color : Asset.Colors.Label.secondary.color + let tintColor = isHighlight ? Asset.Colors.systemOrange.color : UIColor.black.withAlphaComponent(0.6) starButton.tintColor = tintColor starButton.setTitleColor(tintColor, for: .normal) starButton.setTitleColor(tintColor, for: .highlighted) @@ -193,9 +188,23 @@ extension ActionToolbarContainer { os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) delegate?.actionToolbarContainer(self, moreButtonDidPressed: sender) } - @objc private func bookmarkButtonDidPressed(_ sender: UIButton) { - os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - delegate?.actionToolbarContainer(self, bookmarkButtonDidPressed: sender) - } } + +#if DEBUG +import SwiftUI + +struct ActionToolbarContainer_Previews: PreviewProvider { + static var previews: some View { + Group { + UIViewPreview(width: 300) { + let toolbar = ActionToolbarContainer() + toolbar.configure(for: .inline) + return toolbar + } + .previewLayout(.fixed(width: 300, height: 44)) + .previewDisplayName("Inline") + } + } +} +#endif