feat: update status cell UI
This commit is contained in:
parent
0ffc0019a4
commit
40a524434f
|
@ -19,6 +19,9 @@
|
|||
"preview": "Preview",
|
||||
"open_in_safari": "Open in Safari"
|
||||
},
|
||||
"status": {
|
||||
"userBoosted": "%s boosted"
|
||||
},
|
||||
"timeline": {
|
||||
"load_more": "Load More"
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
"preview": "Preview",
|
||||
"open_in_safari": "Open in Safari"
|
||||
},
|
||||
"status": {
|
||||
"userBoosted": "%s boosted"
|
||||
},
|
||||
"timeline": {
|
||||
"load_more": "Load More"
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
}()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue