diff --git a/Package.swift b/Package.swift index 6b71031..c8c0699 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/ivanvorobei/SPAlert", .upToNextMajor(from: "4.2.0")), - .package(url: "https://github.com/ivanvorobei/NativeUIKit", .upToNextMajor(from: "1.3.8")), + .package(url: "https://github.com/ivanvorobei/NativeUIKit", .upToNextMajor(from: "1.3.9")), .package(url: "https://github.com/ivanvorobei/SPFirebase", .upToNextMajor(from: "1.0.6")), .package(url: "https://github.com/sparrowcode/SPSafeSymbols", .upToNextMajor(from: "1.0.5")), .package(url: "https://github.com/kean/Nuke", .upToNextMajor(from: "10.7.1")) diff --git a/Sources/SPProfiling/Interface/Auth/AuthController.swift b/Sources/SPProfiling/Interface/Auth/AuthController.swift new file mode 100644 index 0000000..fda0e95 --- /dev/null +++ b/Sources/SPProfiling/Interface/Auth/AuthController.swift @@ -0,0 +1,71 @@ +// The MIT License (MIT) +// Copyright © 2022 Ivan Vorobei (hello@ivanvorobei.by) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import UIKit +import NativeUIKit +import SparrowKit +import SPAlert + +public class AuthController: NativeOnboardingFeaturesController { + + private var completion: ()->Void + + // MARK: - Views + + let actionToolbarView = NativeAppleAuthToolBarView() + + // MARK: - Init + + init(title: String, description: String, completion: @escaping ()->Void) { + self.completion = completion + super.init( + iconImage: NativeAvatarView.generatePlaceholderImage(fontSize: 80, fontWeight: .medium), + title: title, + subtitle: description + ) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Lifecycle + + public override func viewDidLoad() { + super.viewDidLoad() + + if let navigationController = self.navigationController as? NativeNavigationController { + navigationController.mimicrateToolBarView = actionToolbarView + } + + actionToolbarView.authButton.addTarget(self, action: #selector(self.tapSignInApple), for: .touchUpInside) + } + + @objc func tapSignInApple() { + ProfileModel.signInApple(on: self) { error in + if let error = error { + SPAlert.present(message: error.localizedDescription, haptic: .error) + } else { + self.completion() + } + } + } +} diff --git a/Sources/SPProfiling/Interface/Profile/Table/CellProvider+Profile.swift b/Sources/SPProfiling/Interface/Profile/Table/CellProvider+Profile.swift index 0be35d7..beb668a 100644 --- a/Sources/SPProfiling/Interface/Profile/Table/CellProvider+Profile.swift +++ b/Sources/SPProfiling/Interface/Profile/Table/CellProvider+Profile.swift @@ -26,8 +26,10 @@ extension SPDiffableTableDataSource.CellProvider { public static var profile: SPDiffableTableDataSource.CellProvider { return SPDiffableTableDataSource.CellProvider() { (tableView, indexPath, item) -> UITableViewCell? in - guard let _ = item as? DiffableProfileItem else { return nil } + guard let item = item as? DiffableProfileItem else { return nil } let cell = tableView.dequeueReusableCell(withClass: ProfileTableViewCell.self, for: indexPath) + cell.profileLabelsView.descriptionLabel.text = item.cellProfileSubtitle + cell.authLabelsView.descriptionLabel.text = item.cellAuthSubtitle return cell } } diff --git a/Sources/SPProfiling/Interface/Profile/Table/DiffableProfileItem.swift b/Sources/SPProfiling/Interface/Profile/Table/DiffableProfileItem.swift index c970c56..50038a9 100644 --- a/Sources/SPProfiling/Interface/Profile/Table/DiffableProfileItem.swift +++ b/Sources/SPProfiling/Interface/Profile/Table/DiffableProfileItem.swift @@ -21,12 +21,38 @@ import UIKit import SPDiffable +import NativeUIKit open class DiffableProfileItem: SPDiffableActionableItem { public static let id: String = "spprofiling-profile-item" - public init(action: SPDiffableActionableItem.Action? = nil) { - super.init(id: Self.id, action: action) + var cellAuthSubtitle: String + var cellProfileSubtitle: String + + public init( + authTitle: String, + authDescription: String, + cellAuthSubtitle: String, + cellProfileSubtitle: String, + features: [NativeOnboardingFeatureView.FeatureModel], + completion: @escaping ()->Void, + presentOn controller: UIViewController + ) { + self.cellAuthSubtitle = cellAuthSubtitle + self.cellProfileSubtitle = cellProfileSubtitle + super.init(id: Self.id, action: { item, indexPath in + if ProfileModel.isAnonymous ?? true { + ProfileModel.showAuth( + title: authTitle, + description: authDescription, + features: features, + completion: completion, + on: controller + ) + } else { + ProfileModel.showCurrentProfile(on: controller) + } + }) } } diff --git a/Sources/SPProfiling/Interface/Profile/Table/ProfileTableViewCell.swift b/Sources/SPProfiling/Interface/Profile/Table/ProfileTableViewCell.swift index cfc9b90..273b3aa 100644 --- a/Sources/SPProfiling/Interface/Profile/Table/ProfileTableViewCell.swift +++ b/Sources/SPProfiling/Interface/Profile/Table/ProfileTableViewCell.swift @@ -25,21 +25,15 @@ import SparrowKit open class ProfileTableViewCell: SPTableViewCell { - public let titleLabel = SPLabel().do { - $0.numberOfLines = 1 - $0.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold) - $0.textColor = .label - } + // MARK: - Views - public let descriptionLabel = SPLabel().do { - $0.numberOfLines = 1 - $0.font = UIFont.preferredFont(forTextStyle: .footnote, weight: .regular) - $0.textColor = .secondaryLabel - } + public let profileLabelsView = ProfileLabelsView() + + public let authLabelsView = AuthLabelsView() public let avatarView = NativeAvatarView().do { $0.isEditable = false - $0.placeholderImage = UIImage.system("person.crop.circle.fill", font: .systemFont(ofSize: 52, weight: .medium)) + $0.placeholderImage = NativeAvatarView.generatePlaceholderImage(fontSize: 46, fontWeight: .medium) $0.avatarAppearance = .placeholder } @@ -48,9 +42,10 @@ open class ProfileTableViewCell: SPTableViewCell { open override func commonInit() { super.commonInit() higlightStyle = .content - contentView.addSubviews(avatarView, titleLabel, descriptionLabel) + contentView.addSubviews(avatarView, profileLabelsView, authLabelsView) accessoryType = .disclosureIndicator updateAppearance() + configureObservers() } open override func prepareForReuse() { @@ -65,11 +60,12 @@ open class ProfileTableViewCell: SPTableViewCell { // MARK: - Ovveride + #warning("change to subbiews") open override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) let higlightContent = (higlightStyle == .content) if higlightContent { - [avatarView, titleLabel, descriptionLabel].forEach({ $0?.alpha = highlighted ? 0.6 : 1 }) + [avatarView, profileLabelsView, authLabelsView].forEach({ $0?.alpha = highlighted ? 0.6 : 1 }) } } @@ -81,28 +77,24 @@ open class ProfileTableViewCell: SPTableViewCell { avatarView.setXToSuperviewLeftMargin() avatarView.frame.origin.y = contentView.layoutMargins.top + let visibleLabelsView = profileLabelsView.isHidden ? authLabelsView : profileLabelsView let avatarRightSpace: CGFloat = NativeLayout.Spaces.default - let labelVerticalSpace: CGFloat = NativeLayout.Spaces.step / 2 - let labelWidth = contentView.layoutWidth - avatarView.frame.width - avatarRightSpace - titleLabel.layoutDynamicHeight(width: labelWidth) - descriptionLabel.layoutDynamicHeight(width: labelWidth) - - titleLabel.frame.origin.x = avatarView.frame.maxX + avatarRightSpace - descriptionLabel.frame.origin.x = titleLabel.frame.origin.x + let labelsWidth = contentView.layoutWidth - avatarView.frame.width - avatarRightSpace + visibleLabelsView.frame.setWidth(labelsWidth) + visibleLabelsView.sizeToFit() + visibleLabelsView.frame.origin.x = avatarView.frame.maxX + avatarRightSpace - let labelHeight = titleLabel.frame.height + labelVerticalSpace + descriptionLabel.frame.height - if (avatarView.frame.origin.y + labelHeight) > avatarView.frame.maxY { - titleLabel.frame.origin.y = contentView.layoutMargins.top + if (avatarView.frame.origin.y + visibleLabelsView.frame.height) > avatarView.frame.maxY { + visibleLabelsView.frame.origin.y = contentView.layoutMargins.top } else { - titleLabel.frame.origin.y = contentView.layoutMargins.top + (contentView.layoutHeight - labelHeight) / 2 + visibleLabelsView.frame.origin.y = contentView.layoutMargins.top + (contentView.layoutHeight - visibleLabelsView.frame.height) / 2 } - - descriptionLabel.frame.origin.y = titleLabel.frame.maxY + labelVerticalSpace } open override func sizeThatFits(_ size: CGSize) -> CGSize { layoutSubviews() - return .init(width: size.width, height: max(avatarView.frame.maxY, descriptionLabel.frame.maxY) + contentView.layoutMargins.bottom) + let visibleLabelsView = profileLabelsView.isHidden ? authLabelsView : profileLabelsView + return .init(width: size.width, height: max(avatarView.frame.maxY, visibleLabelsView.frame.maxY) + contentView.layoutMargins.bottom) } // MARK: - Internal @@ -124,23 +116,64 @@ open class ProfileTableViewCell: SPTableViewCell { } internal func updateAppearance() { - if ProfileModel.isAuthed, let profileModel = ProfileModel.currentProfile { - setProfile(profileModel, completion: nil) + let profileModel = ProfileModel.currentProfile + authLabelsView.titleLabel.text = Texts.Auth.sign_in + profileLabelsView.titleLabel.text = profileModel?.name ?? profileModel?.email ?? Texts.Profile.placeholder_name + + if ProfileModel.isAnonymous ?? true { + avatarView.avatarAppearance = .placeholder + authLabelsView.isHidden = false + profileLabelsView.isHidden = true } else { - setAuthAppearance() + guard let profileModel = ProfileModel.currentProfile else { return } + avatarView.setAvatar(of: profileModel) + authLabelsView.isHidden = true + profileLabelsView.isHidden = false } + + layoutSubviews() } - internal func setAuthAppearance() { - avatarView.avatarAppearance = .placeholder - titleLabel.text = Texts.Auth.sign_in - titleLabel.textColor = .tint + // MARK: - Views + + public class ProfileLabelsView: SPView { + + public let titleLabel = SPLabel().do { + $0.numberOfLines = 1 + $0.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold) + $0.textColor = .label + } + + public let descriptionLabel = SPLabel().do { + $0.numberOfLines = 1 + $0.font = UIFont.preferredFont(forTextStyle: .footnote, weight: .regular) + $0.textColor = .secondaryLabel + } + + public override func commonInit() { + super.commonInit() + layoutMargins = .zero + addSubview(titleLabel) + addSubview(descriptionLabel) + } + + public override func layoutSubviews() { + super.layoutSubviews() + titleLabel.layoutDynamicHeight(x: .zero, y: .zero, width: frame.width) + descriptionLabel.layoutDynamicHeight(x: .zero, y: titleLabel.frame.maxY + 2, width: frame.width) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + layoutSubviews() + return .init(width: size.width, height: descriptionLabel.frame.maxY) + } } - internal func setProfile(_ profileModel: ProfileModel, completion: (()->())? = nil) { - titleLabel.text = profileModel.name ?? profileModel.email ?? Texts.Profile.placeholder_name - avatarView.setAvatar(of: profileModel) { - completion?() + public class AuthLabelsView: ProfileLabelsView { + + public override func commonInit() { + super.commonInit() + titleLabel.textColor = .tint } } } diff --git a/Sources/SPProfiling/ProfileModelExtension.swift b/Sources/SPProfiling/ProfileModelExtension.swift index 6906b53..d82ca5d 100644 --- a/Sources/SPProfiling/ProfileModelExtension.swift +++ b/Sources/SPProfiling/ProfileModelExtension.swift @@ -97,6 +97,31 @@ extension ProfileModel { viewController.present(navigationController) } + public static func showAuth( + title: String, + description: String, + features: [NativeOnboardingFeatureView.FeatureModel], + completion: @escaping ()->Void, + on viewController: UIViewController + ) { + let controller = AuthController(title: title, description: description, completion: completion) + controller.setFeatures(features) + let navigationController = NativeNavigationController(rootViewController: controller) + controller.navigationItem.rightBarButtonItem = controller.closeBarButtonItem + + let horizontalMargin: CGFloat = NativeLayout.Spaces.Margins.modal_screen_horizontal + controller.modalPresentationStyle = .formSheet + controller.preferredContentSize = .init(width: 540, height: 620) + controller.view.layoutMargins.left = horizontalMargin + controller.view.layoutMargins.right = horizontalMargin + + navigationController.inheritLayoutMarginsForNavigationBar = true + navigationController.inheritLayoutMarginsForСhilds = true + navigationController.viewDidLayoutSubviews() + + viewController.present(navigationController) + } + // MARK: - Middleware