feat: update status cell UI

This commit is contained in:
CMK 2021-02-23 15:16:55 +08:00
parent 0ffc0019a4
commit 40a524434f
33 changed files with 654 additions and 388 deletions

View File

@ -19,6 +19,9 @@
"preview": "Preview",
"open_in_safari": "Open in Safari"
},
"status": {
"userBoosted": "%s boosted"
},
"timeline": {
"load_more": "Load More"
}

View File

@ -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";

View File

@ -19,6 +19,9 @@
"preview": "Preview",
"open_in_safari": "Open in Safari"
},
"status": {
"userBoosted": "%s boosted"
},
"timeline": {
"load_more": "Load More"
}

View File

@ -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 = "<group>"; };
2D152A8B25C295CC009AA50C /* TimelinePostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePostView.swift; sourceTree = "<group>"; };
2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = "<group>"; };
2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
@ -211,7 +212,6 @@
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = "<group>"; };
2D46976325C2A71500CF4AA9 /* UIIamge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIIamge.swift; sourceTree = "<group>"; };
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlContainableScrollViews.swift; sourceTree = "<group>"; };
2D5A3D1025CF87AA002347D6 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = "<group>"; };
2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewContainer.swift; sourceTree = "<group>"; };
2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+DebugAction.swift"; sourceTree = "<group>"; };
@ -224,7 +224,7 @@
2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+StatusProvider.swift"; sourceTree = "<group>"; };
2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
2D76319E25C1521200929FB9 /* TimelineSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSection.swift; sourceTree = "<group>"; };
2D7631A725C1535600929FB9 /* TimelinePostTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePostTableViewCell.swift; sourceTree = "<group>"; };
2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
2D927F0125C7E4F2004F19B8 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
2D927F0725C7E9A8004F19B8 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
@ -256,6 +256,8 @@
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
DB084B5625CBC56C00F898ED /* Toot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toot.swift; sourceTree = "<group>"; };
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; };
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
@ -393,7 +395,7 @@
2D152A8A25C295B8009AA50C /* Content */ = {
isa = PBXGroup;
children = (
2D152A8B25C295CC009AA50C /* TimelinePostView.swift */,
2D152A8B25C295CC009AA50C /* StatusView.swift */,
);
path = Content;
sourceTree = "<group>";
@ -436,7 +438,7 @@
children = (
DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */,
2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */,
2D5A3D1025CF87AA002347D6 /* AvatarBarButtonItem.swift */,
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */,
);
path = Button;
sourceTree = "<group>";
@ -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;

View File

@ -21,7 +21,7 @@ extension TimelineSection {
dependency: NeedsDependency,
managedObjectContext: NSManagedObjectContext,
timestampUpdatePublisher: AnyPublisher<Date, Never>,
timelinePostTableViewCellDelegate: TimelinePostTableViewCellDelegate,
timelinePostTableViewCellDelegate: StatusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
) -> UITableViewDiffableDataSource<TimelineSection, Item> {
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<Date, Never>,
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)

View File

@ -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
}

View File

@ -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")
}

View File

@ -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")

View File

@ -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
}
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) {
init(avatarImageURL: URL?, placeholderImage: UIImage? = nil) {
self.avatarImageURL = avatarImageURL
self.placeholderImage = placeholderImage
self.blocked = blocked
self.verified = verified
}
}
let input: Input
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tiraya-adam-QfHEWqPelsc-unsplash.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -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";

View File

@ -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
}()

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 }

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
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) {
}
}

View File

@ -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<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
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) {
}
}

View File

@ -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