From eda3e95ad0cffebc2d62ebce6841f0a6666cc201 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 2 Mar 2021 12:49:04 +0800 Subject: [PATCH] feat: add poll table view cell --- Mastodon.xcodeproj/project.pbxproj | 10 + Mastodon/Extension/CALayer.swift | 51 +++++ Mastodon/Generated/Assets.swift | 1 + .../Contents.json | 24 ++- .../Contents.json | 6 +- .../Label/secondary.colorset/Contents.json | 6 +- .../lightSecondaryText.colorset/Contents.json | 6 +- .../TableviewCell/PollTableViewCell.swift | 200 ++++++++++++++++++ 8 files changed, 292 insertions(+), 12 deletions(-) create mode 100644 Mastodon/Extension/CALayer.swift create mode 100644 Mastodon/Scene/Share/View/TableviewCell/PollTableViewCell.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 9b5894130..0415385bb 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -106,6 +106,7 @@ DB427DE225BAA00100D1B89D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DE025BAA00100D1B89D /* LaunchScreen.storyboard */; }; DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; }; DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; }; + DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44384E25E8C1FA008912A2 /* CALayer.swift */; }; DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */; }; DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; }; DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; }; @@ -147,6 +148,8 @@ DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */; }; DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; }; DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */; }; + DB92CF7225E7BB98002C1017 /* PollTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB92CF7125E7BB98002C1017 /* PollTableViewCell.swift */; }; + DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */; }; DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; }; DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; }; DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; }; @@ -324,6 +327,7 @@ DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastodonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = ""; }; DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DB44384E25E8C1FA008912A2 /* CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CALayer.swift; sourceTree = ""; }; DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardResponderService.swift; sourceTree = ""; }; DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = ""; }; DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = ""; }; @@ -366,6 +370,8 @@ DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineIndex.swift; sourceTree = ""; }; + DB92CF7125E7BB98002C1017 /* PollTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableViewCell.swift; sourceTree = ""; }; + DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = ""; }; DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = ""; }; DB98337E25C9452D00AD9700 /* APIService+APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+APIError.swift"; sourceTree = ""; }; @@ -669,6 +675,7 @@ 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */, 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */, 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */, + DB92CF7125E7BB98002C1017 /* PollTableViewCell.swift */, ); path = TableviewCell; sourceTree = ""; @@ -997,6 +1004,7 @@ isa = PBXGroup; children = ( DB084B5125CBC56300F898ED /* CoreDataStack */, + DB44384E25E8C1FA008912A2 /* CALayer.swift */, DB0140CE25C42AEE00F9F3CF /* OSLog.swift */, 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */, DB8AF55C25C138B7002E6C99 /* UIViewController.swift */, @@ -1448,6 +1456,7 @@ 2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */, 0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */, DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */, + DB92CF7225E7BB98002C1017 /* PollTableViewCell.swift in Sources */, DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */, 2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */, 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */, @@ -1508,6 +1517,7 @@ 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */, 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, + DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */, 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, 2D650FAB25ECDC9300851B58 /* Mastodon+Entidy+ErrorDetailReason.swift in Sources */, DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */, diff --git a/Mastodon/Extension/CALayer.swift b/Mastodon/Extension/CALayer.swift new file mode 100644 index 000000000..41ce739ee --- /dev/null +++ b/Mastodon/Extension/CALayer.swift @@ -0,0 +1,51 @@ +// +// CALayer.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-2-26. +// + +import UIKit + +extension CALayer { + + func setupShadow( + color: UIColor = .black, + alpha: Float = 0.5, + x: CGFloat = 0, + y: CGFloat = 2, + blur: CGFloat = 4, + spread: CGFloat = 0, + roundedRect: CGRect? = nil, + byRoundingCorners corners: UIRectCorner? = nil, + cornerRadii: CGSize? = nil + ) { + // assert(roundedRect != .zero) + shadowColor = color.cgColor + shadowOpacity = alpha + shadowOffset = CGSize(width: x, height: y) + shadowRadius = blur / 2 + rasterizationScale = UIScreen.main.scale + shouldRasterize = true + masksToBounds = false + + guard let roundedRect = roundedRect, + let corners = corners, + let cornerRadii = cornerRadii else { + return + } + + if spread == 0 { + shadowPath = UIBezierPath(roundedRect: roundedRect, byRoundingCorners: corners, cornerRadii: cornerRadii).cgPath + } else { + let rect = roundedRect.insetBy(dx: -spread, dy: -spread) + shadowPath = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii).cgPath + } + } + + func removeShadow() { + shadowRadius = 0 + } + + +} diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift index 08507ed9d..a4f4e0803 100644 --- a/Mastodon/Generated/Assets.swift +++ b/Mastodon/Generated/Assets.swift @@ -71,6 +71,7 @@ internal enum Asset { internal enum Welcome { internal static let mastodonLogo = ImageAsset(name: "Welcome/mastodon.logo") internal static let mastodonLogoLarge = ImageAsset(name: "Welcome/mastodon.logo.large") + internal static let welcomeLogo = ImageAsset(name: "Welcome/welcome.logo") } } // swiftlint:enable identifier_name line_length nesting type_body_length type_name diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.system.background.colorset/Contents.json index 7e0375939..91dac809a 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.system.background.colorset/Contents.json @@ -5,9 +5,27 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x37", - "green" : "0x2D", - "red" : "0x29" + "blue" : "0xE8", + "green" : "0xE1", + "red" : "0xD9" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.216", + "green" : "0.176", + "red" : "0.161" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json index edc0dce9a..d097fec40 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "232", - "green" : "225", - "red" : "217" + "blue" : "0xE8", + "green" : "0xE1", + "red" : "0xD9" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json index 70b1446d0..8953c8fb0 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "0.600", - "blue" : "0x43", - "green" : "0x3C", - "red" : "0x3C" + "blue" : "67", + "green" : "60", + "red" : "60" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/lightSecondaryText.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/lightSecondaryText.colorset/Contents.json index 5fb782c4f..ba375b791 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/lightSecondaryText.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/lightSecondaryText.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "0.600", - "blue" : "0.263", - "green" : "0.235", - "red" : "0.235" + "blue" : "67", + "green" : "60", + "red" : "60" } }, "idiom" : "universal" diff --git a/Mastodon/Scene/Share/View/TableviewCell/PollTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/PollTableViewCell.swift new file mode 100644 index 000000000..d41fd7428 --- /dev/null +++ b/Mastodon/Scene/Share/View/TableviewCell/PollTableViewCell.swift @@ -0,0 +1,200 @@ +// +// PollTableViewCell.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-2-25. +// + +import UIKit + +final class PollTableViewCell: UITableViewCell { + + static let checkmarkImageSize = CGSize(width: 26, height: 26) + + let roundedBackgroundView = UIView() + + let checkmarkBackgroundView: UIView = { + let view = UIView() + view.backgroundColor = .systemBackground + return view + }() + + let checkmarkImageView: UIView = { + let imageView = UIImageView() + let image = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .bold))! + imageView.image = image.withRenderingMode(.alwaysTemplate) + imageView.tintColor = Asset.Colors.Button.highlight.color + return imageView + }() + + let optionLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15, weight: .medium) + label.textColor = Asset.Colors.Label.primary.color + label.text = "Option" + label.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .left : .right + return label + }() + + let optionPercentageLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 13, weight: .regular) + label.textColor = Asset.Colors.Label.primary.color + label.text = "50%" + label.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .right : .left + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension PollTableViewCell { + + private func _init() { + roundedBackgroundView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + + roundedBackgroundView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(roundedBackgroundView) + NSLayoutConstraint.activate([ + roundedBackgroundView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5), + roundedBackgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + roundedBackgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor, constant: 5), + ]) + + checkmarkBackgroundView.translatesAutoresizingMaskIntoConstraints = false + roundedBackgroundView.addSubview(checkmarkBackgroundView) + NSLayoutConstraint.activate([ + checkmarkBackgroundView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor, constant: 9), + checkmarkBackgroundView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor, constant: 9), + roundedBackgroundView.bottomAnchor.constraint(equalTo: checkmarkBackgroundView.bottomAnchor, constant: 9), + checkmarkBackgroundView.widthAnchor.constraint(equalToConstant: PollTableViewCell.checkmarkImageSize.width).priority(.defaultHigh), + checkmarkBackgroundView.heightAnchor.constraint(equalToConstant: PollTableViewCell.checkmarkImageSize.height).priority(.defaultHigh), + ]) + + checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false + checkmarkBackgroundView.addSubview(checkmarkImageView) + NSLayoutConstraint.activate([ + checkmarkImageView.topAnchor.constraint(equalTo: checkmarkBackgroundView.topAnchor, constant: 5), + checkmarkImageView.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.leadingAnchor, constant: 5), + checkmarkBackgroundView.trailingAnchor.constraint(equalTo: checkmarkImageView.trailingAnchor, constant: 5), + checkmarkBackgroundView.bottomAnchor.constraint(equalTo: checkmarkImageView.bottomAnchor, constant: 5), + ]) + + optionLabel.translatesAutoresizingMaskIntoConstraints = false + roundedBackgroundView.addSubview(optionLabel) + NSLayoutConstraint.activate([ + optionLabel.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.trailingAnchor, constant: 14), + optionLabel.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor), + ]) + + optionPercentageLabel.translatesAutoresizingMaskIntoConstraints = false + roundedBackgroundView.addSubview(optionPercentageLabel) + NSLayoutConstraint.activate([ + optionPercentageLabel.leadingAnchor.constraint(equalTo: optionLabel.trailingAnchor, constant: 8), + roundedBackgroundView.trailingAnchor.constraint(equalTo: optionPercentageLabel.trailingAnchor, constant: 18), + optionPercentageLabel.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor), + ]) + + configureCheckmark(state: .none) + } + + override func layoutSubviews() { + super.layoutSubviews() + + roundedBackgroundView.layer.masksToBounds = true + roundedBackgroundView.layer.cornerRadius = roundedBackgroundView.bounds.height * 0.5 + roundedBackgroundView.layer.cornerCurve = .circular + + checkmarkBackgroundView.layer.masksToBounds = true + checkmarkBackgroundView.layer.cornerRadius = checkmarkBackgroundView.bounds.height * 0.5 + checkmarkBackgroundView.layer.cornerCurve = .circular + } + +} + +extension PollTableViewCell { + + enum CheckmarkState { + case none + case off + case on + } + + func configureCheckmark(state: CheckmarkState) { + switch state { + case .none: + checkmarkBackgroundView.backgroundColor = .clear + checkmarkImageView.isHidden = true + optionPercentageLabel.isHidden = true + optionLabel.textColor = Asset.Colors.Label.primary.color + optionLabel.layer.removeShadow() + case .off: + checkmarkBackgroundView.backgroundColor = .systemBackground + checkmarkBackgroundView.layer.borderColor = UIColor.systemGray3.cgColor + checkmarkBackgroundView.layer.borderWidth = 1 + checkmarkImageView.isHidden = true + optionPercentageLabel.isHidden = true + optionLabel.textColor = Asset.Colors.Label.primary.color + optionLabel.layer.removeShadow() + case .on: + checkmarkBackgroundView.backgroundColor = .systemBackground + checkmarkBackgroundView.layer.borderColor = UIColor.clear.cgColor + checkmarkBackgroundView.layer.borderWidth = 0 + checkmarkImageView.isHidden = false + optionPercentageLabel.isHidden = false + optionLabel.textColor = .white + optionLabel.layer.setupShadow(x: 0, y: 0, blur: 4, spread: 0) + } + } + +} + + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +struct PollTableViewCell_Previews: PreviewProvider { + + static var controls: some View { + Group { + UIViewPreview() { + PollTableViewCell() + } + .previewLayout(.fixed(width: 375, height: 44 + 10)) + UIViewPreview() { + let cell = PollTableViewCell() + cell.configureCheckmark(state: .off) + return cell + } + .previewLayout(.fixed(width: 375, height: 44 + 10)) + UIViewPreview() { + let cell = PollTableViewCell() + cell.configureCheckmark(state: .on) + return cell + } + .previewLayout(.fixed(width: 375, height: 44 + 10)) + } + } + + static var previews: some View { + Group { + controls.colorScheme(.light) + controls.colorScheme(.dark) + } + .background(Color.gray) + } + +} + +#endif +