From 376cb3d58aee1190966c485a2f76d4620682f8dc Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Mar 2021 19:33:33 +0800 Subject: [PATCH] feat: display toot poll status --- Localization/app.json | 9 ++- .../Diffiable/Section/StatusSection.swift | 19 +++--- Mastodon/Generated/Strings.swift | 18 +++++- ...er+TimelinePostTableViewCellDelegate.swift | 4 +- .../Resources/en.lproj/Localizable.strings | 5 +- .../Scene/Share/View/Content/StatusView.swift | 58 ++++++++++++++----- .../TableviewCell/StatusTableViewCell.swift | 4 +- 7 files changed, 87 insertions(+), 30 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 3a45922a5..7b118b34d 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -58,7 +58,14 @@ "user_boosted": "%s boosted", "show_post": "Show Post", "status_content_warning": "content warning", - "media_content_warning": "Tap to reveal that may be sensitive" + "media_content_warning": "Tap to reveal that may be sensitive", + "poll": { + "vote_count": { + "single": "%d vote", + "multiple": "%d votes", + }, + "time_left": "%s left" + } }, "timeline": { "load_more": "Load More" diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index b8838869c..2fd87f4d1 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -132,14 +132,14 @@ extension StatusSection { }() if mosiacImageViewModel.metas.count == 1 { let meta = mosiacImageViewModel.metas[0] - let imageView = cell.statusView.statusMosaicImageView.setupImageView(aspectRatio: meta.size, maxSize: imageViewMaxSize) + let imageView = cell.statusView.statusMosaicImageViewContainer.setupImageView(aspectRatio: meta.size, maxSize: imageViewMaxSize) imageView.af.setImage( withURL: meta.url, placeholderImage: UIImage.placeholder(color: .systemFill), imageTransition: .crossDissolve(0.2) ) } else { - let imageViews = cell.statusView.statusMosaicImageView.setupImageViews(count: mosiacImageViewModel.metas.count, maxHeight: imageViewMaxSize.height) + let imageViews = cell.statusView.statusMosaicImageViewContainer.setupImageViews(count: mosiacImageViewModel.metas.count, maxHeight: imageViewMaxSize.height) for (i, imageView) in imageViews.enumerated() { let meta = mosiacImageViewModel.metas[i] imageView.af.setImage( @@ -149,18 +149,19 @@ extension StatusSection { ) } } - cell.statusView.statusMosaicImageView.isHidden = mosiacImageViewModel.metas.isEmpty + cell.statusView.statusMosaicImageViewContainer.isHidden = mosiacImageViewModel.metas.isEmpty let isStatusSensitive = statusContentWarningAttribute?.isStatusSensitive ?? (toot.reblog ?? toot).sensitive - cell.statusView.statusMosaicImageView.blurVisualEffectView.effect = isStatusSensitive ? MosaicImageViewContainer.blurVisualEffect : nil - cell.statusView.statusMosaicImageView.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0 + cell.statusView.statusMosaicImageViewContainer.blurVisualEffectView.effect = isStatusSensitive ? MosaicImageViewContainer.blurVisualEffect : nil + cell.statusView.statusMosaicImageViewContainer.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0 // set poll if let poll = (toot.reblog ?? toot).poll { - cell.statusView.statusPollTableView.isHidden = false + cell.statusView.pollTableView.isHidden = false + cell.statusView.pollStatusStackView.isHidden = false let managedObjectContext = toot.managedObjectContext! cell.statusView.statusPollTableViewDataSource = PollSection.tableViewDiffableDataSource( - for: cell.statusView.statusPollTableView, + for: cell.statusView.pollTableView, managedObjectContext: managedObjectContext ) @@ -176,9 +177,9 @@ extension StatusSection { } snapshot.appendItems(pollItems, toSection: .main) cell.statusView.statusPollTableViewDataSource?.apply(snapshot, animatingDifferences: false, completion: nil) - // cell.statusView.statusPollTableView.layoutIfNeeded() } else { - cell.statusView.statusPollTableView.isHidden = true + cell.statusView.pollTableView.isHidden = true + cell.statusView.pollStatusStackView.isHidden = true } // toolbar diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 8e93c804e..c049f4fca 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -68,6 +68,22 @@ internal enum L10n { internal static func userBoosted(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Controls.Status.UserBoosted", String(describing: p1)) } + internal enum Poll { + /// %@ left + internal static func timeLeft(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Status.Poll.TimeLeft", String(describing: p1)) + } + internal enum VoteCount { + /// %d votes + internal static func multiple(_ p1: Int) -> String { + return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoteCount.Multiple", p1) + } + /// %d vote + internal static func single(_ p1: Int) -> String { + return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoteCount.Single", p1) + } + } + } } internal enum Timeline { /// Load More @@ -124,7 +140,7 @@ internal enum L10n { internal static let passwordTooShrot = L10n.tr("Localizable", "Common.Errors.Itemdetail.PasswordTooShrot") /// Username must only contain alphanumeric characters and underscores internal static let usernameInvalid = L10n.tr("Localizable", "Common.Errors.Itemdetail.UsernameInvalid") - /// username is too long ( can't be longer than 30 characters) + /// username is too long (can't be longer than 30 characters) internal static let usernameTooLong = L10n.tr("Localizable", "Common.Errors.Itemdetail.UsernameTooLong") } } diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift index 336434ff0..4679969e2 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+TimelinePostTableViewCellDelegate.swift @@ -69,8 +69,8 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { var snapshot = diffableDataSource.snapshot() snapshot.reloadItems([item]) UIView.animate(withDuration: 0.33) { - cell.statusView.statusMosaicImageView.blurVisualEffectView.effect = nil - cell.statusView.statusMosaicImageView.vibrancyVisualEffectView.alpha = 0.0 + cell.statusView.statusMosaicImageViewContainer.blurVisualEffectView.effect = nil + cell.statusView.statusMosaicImageViewContainer.vibrancyVisualEffectView.alpha = 0.0 } completion: { _ in diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil) } diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 191ec0daf..9b1dfdf7a 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -17,6 +17,9 @@ "Common.Controls.Actions.SignUp" = "Sign Up"; "Common.Controls.Actions.TakePhoto" = "Take photo"; "Common.Controls.Status.MediaContentWarning" = "Tap to reveal that may be sensitive"; +"Common.Controls.Status.Poll.TimeLeft" = "%@ left"; +"Common.Controls.Status.Poll.VoteCount.Multiple" = "%d votes"; +"Common.Controls.Status.Poll.VoteCount.Single" = "%d vote"; "Common.Controls.Status.ShowPost" = "Show Post"; "Common.Controls.Status.StatusContentWarning" = "content warning"; "Common.Controls.Status.UserBoosted" = "%@ boosted"; @@ -42,7 +45,7 @@ "Common.Errors.Itemdetail.EmailInvalid" = "This is not a valid e-mail address"; "Common.Errors.Itemdetail.PasswordTooShrot" = "password is too short (must be at least 8 characters)"; "Common.Errors.Itemdetail.UsernameInvalid" = "Username must only contain alphanumeric characters and underscores"; -"Common.Errors.Itemdetail.UsernameTooLong" = "username is too long ( can't be longer than 30 characters)"; +"Common.Errors.Itemdetail.UsernameTooLong" = "username is too long (can't be longer than 30 characters)"; "Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "Open Email App"; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Check if your email address is correct as well as your junk folder if you haven’t."; diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 77cc851c1..702d9c7e4 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -103,9 +103,9 @@ final class StatusView: UIView { button.setTitle(L10n.Common.Controls.Status.showPost, for: .normal) return button }() - let statusMosaicImageView = MosaicImageViewContainer() + let statusMosaicImageViewContainer = MosaicImageViewContainer() - let statusPollTableView: UITableView = { + let pollTableView: UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) tableView.register(PollOptionTableViewCell.self, forCellReuseIdentifier: String(describing: PollOptionTableViewCell.self)) tableView.isScrollEnabled = false @@ -114,6 +114,29 @@ final class StatusView: UIView { return tableView }() + let pollStatusStackView = UIStackView() + let pollVoteCountLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 12, weight: .regular)) + label.textColor = Asset.Colors.Label.secondary.color + label.text = L10n.Common.Controls.Status.Poll.VoteCount.single(0) + return label + }() + let pollStatusDotLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 12, weight: .regular)) + label.textColor = Asset.Colors.Label.secondary.color + label.text = " · " + return label + }() + let pollCountdownLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 12, weight: .regular)) + label.textColor = Asset.Colors.Label.secondary.color + label.text = L10n.Common.Controls.Status.Poll.timeLeft("6 hours") + return label + }() + // do not use visual effect view due to we blur text only without background let contentWarningBlurContentImageView: UIImageView = { let imageView = UIImageView() @@ -239,7 +262,7 @@ extension StatusView { subtitleContainerStackView.axis = .horizontal subtitleContainerStackView.addArrangedSubview(usernameLabel) - // status container: [status | image / video | audio | poll] + // status container: [status | image / video | audio | poll | poll status] containerStackView.addArrangedSubview(statusContainerStackView) statusContainerStackView.axis = .vertical statusContainerStackView.spacing = 10 @@ -275,30 +298,37 @@ extension StatusView { statusContentWarningContainerStackView.addArrangedSubview(contentWarningTitle) statusContentWarningContainerStackView.addArrangedSubview(contentWarningActionButton) - statusContainerStackView.addArrangedSubview(statusMosaicImageView) - statusPollTableView.translatesAutoresizingMaskIntoConstraints = false - statusContainerStackView.addArrangedSubview(statusPollTableView) - statusPollTableViewHeightLaoutConstraint = statusPollTableView.heightAnchor.constraint(equalToConstant: 44.0).priority(.required - 1) + statusContainerStackView.addArrangedSubview(statusMosaicImageViewContainer) + pollTableView.translatesAutoresizingMaskIntoConstraints = false + statusContainerStackView.addArrangedSubview(pollTableView) + statusPollTableViewHeightLaoutConstraint = pollTableView.heightAnchor.constraint(equalToConstant: 44.0).priority(.required - 1) NSLayoutConstraint.activate([ statusPollTableViewHeightLaoutConstraint, ]) - statusPollTableViewHeightObservation = statusPollTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] tableView, _ in + statusPollTableViewHeightObservation = pollTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] tableView, _ in guard let self = self else { return } - guard self.statusPollTableView.contentSize.height != .zero else { + guard self.pollTableView.contentSize.height != .zero else { self.statusPollTableViewHeightLaoutConstraint.constant = 44 return } - self.statusPollTableViewHeightLaoutConstraint.constant = self.statusPollTableView.contentSize.height + self.statusPollTableViewHeightLaoutConstraint.constant = self.pollTableView.contentSize.height }) + statusContainerStackView.addArrangedSubview(pollStatusStackView) + pollStatusStackView.axis = .horizontal + pollStatusStackView.addArrangedSubview(pollVoteCountLabel) + pollStatusStackView.addArrangedSubview(pollStatusDotLabel) + pollStatusStackView.addArrangedSubview(pollCountdownLabel) + // action toolbar container containerStackView.addArrangedSubview(actionToolbarContainer) actionToolbarContainer.setContentCompressionResistancePriority(.defaultLow, for: .vertical) headerContainerStackView.isHidden = true - statusMosaicImageView.isHidden = true - statusPollTableView.isHidden = true + statusMosaicImageViewContainer.isHidden = true + pollTableView.isHidden = true + pollStatusStackView.isHidden = true contentWarningBlurContentImageView.isHidden = true statusContentWarningContainerStackView.isHidden = true @@ -390,11 +420,11 @@ struct StatusView_Previews: PreviewProvider { statusView.drawContentWarningImageView() statusView.updateContentWarningDisplay(isHidden: false) let images = MosaicImageView_Previews.images - let imageViews = statusView.statusMosaicImageView.setupImageViews(count: 4, maxHeight: 162) + let imageViews = statusView.statusMosaicImageViewContainer.setupImageViews(count: 4, maxHeight: 162) for (i, imageView) in imageViews.enumerated() { imageView.image = images[i] } - statusView.statusMosaicImageView.isHidden = false + statusView.statusMosaicImageViewContainer.isHidden = false return statusView } .previewLayout(.fixed(width: 375, height: 380)) diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index 900094c57..326687b10 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -32,7 +32,7 @@ final class StatusTableViewCell: UITableViewCell { override func prepareForReuse() { super.prepareForReuse() statusView.isStatusTextSensitive = false - statusView.statusPollTableView.dataSource = nil + statusView.pollTableView.dataSource = nil statusView.cleanUpContentWarning() disposeBag.removeAll() observations.removeAll() @@ -85,7 +85,7 @@ extension StatusTableViewCell { bottomPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color statusView.delegate = self - statusView.statusMosaicImageView.delegate = self + statusView.statusMosaicImageViewContainer.delegate = self statusView.actionToolbarContainer.delegate = self }