feat: update wizard for new iPad design
This commit is contained in:
parent
30b2a35b84
commit
865718351d
|
@ -315,12 +315,12 @@
|
||||||
DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */; };
|
DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */; };
|
||||||
DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */; };
|
DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */; };
|
||||||
DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */; };
|
DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */; };
|
||||||
DB647C5726F1E97300F7F82C /* MainTabBarController+Wizard.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB647C5626F1E97300F7F82C /* MainTabBarController+Wizard.swift */; };
|
|
||||||
DB647C5926F1EA2700F7F82C /* WizardPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB647C5826F1EA2700F7F82C /* WizardPreference.swift */; };
|
DB647C5926F1EA2700F7F82C /* WizardPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB647C5826F1EA2700F7F82C /* WizardPreference.swift */; };
|
||||||
DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; };
|
DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; };
|
||||||
DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; };
|
DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; };
|
||||||
DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; };
|
DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; };
|
||||||
DB67D08427312970006A36CF /* APIService+Following.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08327312970006A36CF /* APIService+Following.swift */; };
|
DB67D08427312970006A36CF /* APIService+Following.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08327312970006A36CF /* APIService+Following.swift */; };
|
||||||
|
DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08527312E67006A36CF /* WizardViewController.swift */; };
|
||||||
DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; };
|
DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; };
|
||||||
DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
|
DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
|
||||||
DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; };
|
DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; };
|
||||||
|
@ -1135,12 +1135,12 @@
|
||||||
DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewingViewController.swift; sourceTree = "<group>"; };
|
DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewingViewController.swift; sourceTree = "<group>"; };
|
||||||
DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewModel.swift; sourceTree = "<group>"; };
|
DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewModel.swift; sourceTree = "<group>"; };
|
||||||
DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewController+StatusProvider.swift"; sourceTree = "<group>"; };
|
DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewController+StatusProvider.swift"; sourceTree = "<group>"; };
|
||||||
DB647C5626F1E97300F7F82C /* MainTabBarController+Wizard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainTabBarController+Wizard.swift"; sourceTree = "<group>"; };
|
|
||||||
DB647C5826F1EA2700F7F82C /* WizardPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardPreference.swift; sourceTree = "<group>"; };
|
DB647C5826F1EA2700F7F82C /* WizardPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardPreference.swift; sourceTree = "<group>"; };
|
||||||
DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = "<group>"; };
|
DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = "<group>"; };
|
||||||
DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = "<group>"; };
|
DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = "<group>"; };
|
||||||
DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = "<group>"; };
|
DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = "<group>"; };
|
||||||
DB67D08327312970006A36CF /* APIService+Following.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Following.swift"; sourceTree = "<group>"; };
|
DB67D08327312970006A36CF /* APIService+Following.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Following.swift"; sourceTree = "<group>"; };
|
||||||
|
DB67D08527312E67006A36CF /* WizardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardViewController.swift; sourceTree = "<group>"; };
|
||||||
DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = "<group>"; };
|
DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = "<group>"; };
|
||||||
DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = "<group>"; };
|
DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = "<group>"; };
|
||||||
|
@ -2529,6 +2529,14 @@
|
||||||
path = Image;
|
path = Image;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
DB67D08727312E6A006A36CF /* Wizard */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DB67D08527312E67006A36CF /* WizardViewController.swift */,
|
||||||
|
);
|
||||||
|
path = Wizard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DB6804802637CD4C00430867 /* AppShared */ = {
|
DB6804802637CD4C00430867 /* AppShared */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2770,7 +2778,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */,
|
DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */,
|
||||||
DB647C5626F1E97300F7F82C /* MainTabBarController+Wizard.swift */,
|
|
||||||
);
|
);
|
||||||
path = MainTab;
|
path = MainTab;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2782,6 +2789,7 @@
|
||||||
DB6180E426391A500018D199 /* Transition */,
|
DB6180E426391A500018D199 /* Transition */,
|
||||||
DB852D1D26FB021900FC9D81 /* Root */,
|
DB852D1D26FB021900FC9D81 /* Root */,
|
||||||
DB01409B25C40BB600F9F3CF /* Onboarding */,
|
DB01409B25C40BB600F9F3CF /* Onboarding */,
|
||||||
|
DB67D08727312E6A006A36CF /* Wizard */,
|
||||||
DB9F58ED26EF435800E7BBE9 /* Account */,
|
DB9F58ED26EF435800E7BBE9 /* Account */,
|
||||||
2D38F1D325CD463600561493 /* HomeTimeline */,
|
2D38F1D325CD463600561493 /* HomeTimeline */,
|
||||||
2D76316325C14BAC00929FB9 /* PublicTimeline */,
|
2D76316325C14BAC00929FB9 /* PublicTimeline */,
|
||||||
|
@ -4358,6 +4366,7 @@
|
||||||
DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */,
|
DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */,
|
||||||
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */,
|
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */,
|
||||||
DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */,
|
DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */,
|
||||||
|
DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */,
|
||||||
DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */,
|
DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */,
|
||||||
DB9A489026035963008B817C /* APIService+Media.swift in Sources */,
|
DB9A489026035963008B817C /* APIService+Media.swift in Sources */,
|
||||||
DBFEF07726A691FB006D7ED1 /* MastodonAuthenticationBox.swift in Sources */,
|
DBFEF07726A691FB006D7ED1 /* MastodonAuthenticationBox.swift in Sources */,
|
||||||
|
@ -4371,7 +4380,6 @@
|
||||||
DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */,
|
DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */,
|
||||||
DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */,
|
DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */,
|
||||||
DB6D9F6F2635807F008423CD /* Setting.swift in Sources */,
|
DB6D9F6F2635807F008423CD /* Setting.swift in Sources */,
|
||||||
DB647C5726F1E97300F7F82C /* MainTabBarController+Wizard.swift in Sources */,
|
|
||||||
DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */,
|
DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */,
|
||||||
DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */,
|
DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */,
|
||||||
DB6B74F8272FBFB100C70B6E /* FollowerListViewController+Provider.swift in Sources */,
|
DB6B74F8272FBFB100C70B6E /* FollowerListViewController+Provider.swift in Sources */,
|
||||||
|
|
|
@ -23,6 +23,7 @@ final public class SceneCoordinator {
|
||||||
|
|
||||||
private(set) weak var tabBarController: MainTabBarController!
|
private(set) weak var tabBarController: MainTabBarController!
|
||||||
private(set) weak var splitViewController: RootSplitViewController?
|
private(set) weak var splitViewController: RootSplitViewController?
|
||||||
|
private(set) var wizardViewController: WizardViewController?
|
||||||
|
|
||||||
private(set) var secondaryStackHashValues = Set<Int>()
|
private(set) var secondaryStackHashValues = Set<Int>()
|
||||||
|
|
||||||
|
@ -221,17 +222,34 @@ extension SceneCoordinator {
|
||||||
extension SceneCoordinator {
|
extension SceneCoordinator {
|
||||||
|
|
||||||
func setup() {
|
func setup() {
|
||||||
|
let rootViewController: UIViewController
|
||||||
switch UIDevice.current.userInterfaceIdiom {
|
switch UIDevice.current.userInterfaceIdiom {
|
||||||
case .phone:
|
case .phone:
|
||||||
let viewController = MainTabBarController(context: appContext, coordinator: self)
|
let viewController = MainTabBarController(context: appContext, coordinator: self)
|
||||||
sceneDelegate.window?.rootViewController = viewController
|
self.splitViewController = nil
|
||||||
tabBarController = viewController
|
self.tabBarController = viewController
|
||||||
|
rootViewController = viewController
|
||||||
default:
|
default:
|
||||||
let splitViewController = RootSplitViewController(context: appContext, coordinator: self)
|
let splitViewController = RootSplitViewController(context: appContext, coordinator: self)
|
||||||
self.splitViewController = splitViewController
|
self.splitViewController = splitViewController
|
||||||
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
|
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
|
||||||
sceneDelegate.window?.rootViewController = splitViewController
|
rootViewController = splitViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wizardViewController = WizardViewController()
|
||||||
|
if !wizardViewController.items.isEmpty,
|
||||||
|
let delegate = rootViewController as? WizardViewControllerDelegate
|
||||||
|
{
|
||||||
|
// do not add as child view controller.
|
||||||
|
// otherwise, the tab bar controller will add as a new tab
|
||||||
|
wizardViewController.delegate = delegate
|
||||||
|
wizardViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
wizardViewController.view.frame = rootViewController.view.bounds
|
||||||
|
rootViewController.view.addSubview(wizardViewController.view)
|
||||||
|
self.wizardViewController = wizardViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneDelegate.window?.rootViewController = rootViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupOnboardingIfNeeds(animated: Bool) {
|
func setupOnboardingIfNeeds(animated: Bool) {
|
||||||
|
|
|
@ -96,6 +96,13 @@ extension WizardCardView {
|
||||||
path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.minY), radius: radius, startAngle: -.pi / 2, endAngle: -.pi, clockwise: false)
|
path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.minY), radius: radius, startAngle: -.pi / 2, endAngle: -.pi, clockwise: false)
|
||||||
path.addLine(to: CGPoint(x: rect.minX - radius, y: rect.maxY + radius + WizardCardView.bubbleArrowHeight))
|
path.addLine(to: CGPoint(x: rect.minX - radius, y: rect.maxY + radius + WizardCardView.bubbleArrowHeight))
|
||||||
path.close()
|
path.close()
|
||||||
|
case .allCorners: // no arrow
|
||||||
|
path.move(to: CGPoint(x: rect.maxX, y: rect.maxY + radius))
|
||||||
|
path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.maxY), radius: radius, startAngle: .pi / 2, endAngle: .pi, clockwise: true)
|
||||||
|
path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.minY), radius: radius, startAngle: .pi, endAngle: .pi / 2 * 3, clockwise: true)
|
||||||
|
path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.minY), radius: radius, startAngle: .pi / 2 * 3, endAngle: .pi * 2, clockwise: true)
|
||||||
|
path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.maxY), radius: radius, startAngle: .pi * 2, endAngle: .pi / 2 * 5, clockwise: true)
|
||||||
|
path.close()
|
||||||
default:
|
default:
|
||||||
assertionFailure("FIXME")
|
assertionFailure("FIXME")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
//
|
|
||||||
// MainTabBarController+Wizard.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021-9-15.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
protocol WizardDelegate: AnyObject {
|
|
||||||
func spotlight(item: MainTabBarController.Wizard.Item) -> UIBezierPath
|
|
||||||
func layoutWizardCard(_ wizard: MainTabBarController.Wizard, item: MainTabBarController.Wizard.Item)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MainTabBarController {
|
|
||||||
class Wizard {
|
|
||||||
|
|
||||||
let logger = Logger(subsystem: "Wizard", category: "UI")
|
|
||||||
|
|
||||||
weak var delegate: WizardDelegate?
|
|
||||||
|
|
||||||
private(set) var items: [Item]
|
|
||||||
|
|
||||||
let backgroundView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
init() {
|
|
||||||
var items: [Item] = []
|
|
||||||
if !UserDefaults.shared.didShowMultipleAccountSwitchWizard {
|
|
||||||
items.append(.multipleAccountSwitch)
|
|
||||||
}
|
|
||||||
self.items = items
|
|
||||||
|
|
||||||
let backgroundTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
|
||||||
backgroundTapGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.Wizard.backgroundTapGestureRecognizerHandler(_:)))
|
|
||||||
backgroundView.addGestureRecognizer(backgroundTapGestureRecognizer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MainTabBarController.Wizard {
|
|
||||||
enum Item {
|
|
||||||
case multipleAccountSwitch
|
|
||||||
|
|
||||||
var title: String {
|
|
||||||
return L10n.Scene.Wizard.newInMastodon
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .multipleAccountSwitch:
|
|
||||||
return L10n.Scene.Wizard.multipleAccountSwitchIntroDescription
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func markAsRead() {
|
|
||||||
switch self {
|
|
||||||
case .multipleAccountSwitch:
|
|
||||||
UserDefaults.shared.didShowMultipleAccountSwitchWizard = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MainTabBarController.Wizard {
|
|
||||||
|
|
||||||
func setup(in view: UIView) {
|
|
||||||
assert(delegate != nil, "need set delegate before use")
|
|
||||||
|
|
||||||
guard !items.isEmpty else { return }
|
|
||||||
|
|
||||||
backgroundView.frame = view.bounds
|
|
||||||
backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.addSubview(backgroundView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
backgroundView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
||||||
backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
func consume() {
|
|
||||||
guard !items.isEmpty else {
|
|
||||||
backgroundView.removeFromSuperview()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let item = items.removeFirst()
|
|
||||||
perform(item: item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func perform(item: Item) {
|
|
||||||
guard let delegate = delegate else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare for reuse
|
|
||||||
prepareForReuse()
|
|
||||||
|
|
||||||
// set wizard item read
|
|
||||||
item.markAsRead()
|
|
||||||
|
|
||||||
// add spotlight
|
|
||||||
let spotlight = delegate.spotlight(item: item)
|
|
||||||
let maskLayer = CAShapeLayer()
|
|
||||||
let path = UIBezierPath(rect: backgroundView.bounds)
|
|
||||||
path.append(spotlight)
|
|
||||||
maskLayer.fillRule = .evenOdd
|
|
||||||
maskLayer.path = path.cgPath
|
|
||||||
backgroundView.layer.mask = maskLayer
|
|
||||||
|
|
||||||
// layout wizard card
|
|
||||||
delegate.layoutWizardCard(self, item: item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func prepareForReuse() {
|
|
||||||
backgroundView.subviews.forEach { subview in
|
|
||||||
subview.removeFromSuperview()
|
|
||||||
}
|
|
||||||
backgroundView.mask = nil
|
|
||||||
backgroundView.layer.mask = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MainTabBarController.Wizard {
|
|
||||||
@objc private func backgroundTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
||||||
|
|
||||||
consume()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,8 +21,6 @@ class MainTabBarController: UITabBarController {
|
||||||
|
|
||||||
static let avatarButtonSize = CGSize(width: 25, height: 25)
|
static let avatarButtonSize = CGSize(width: 25, height: 25)
|
||||||
let avatarButton = CircleAvatarButton()
|
let avatarButton = CircleAvatarButton()
|
||||||
|
|
||||||
let wizard = Wizard()
|
|
||||||
|
|
||||||
var currentTab = CurrentValueSubject<Tab, Never>(.home)
|
var currentTab = CurrentValueSubject<Tab, Never>(.home)
|
||||||
|
|
||||||
|
@ -108,6 +106,8 @@ class MainTabBarController: UITabBarController {
|
||||||
|
|
||||||
var _viewControllers: [UIViewController] = []
|
var _viewControllers: [UIViewController] = []
|
||||||
|
|
||||||
|
private(set) var isReadyForWizardAvatarButton = false
|
||||||
|
|
||||||
init(context: AppContext, coordinator: SceneCoordinator) {
|
init(context: AppContext, coordinator: SceneCoordinator) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.coordinator = coordinator
|
self.coordinator = coordinator
|
||||||
|
@ -247,9 +247,6 @@ extension MainTabBarController {
|
||||||
profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName)
|
profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
wizard.delegate = self
|
|
||||||
wizard.setup(in: view)
|
|
||||||
|
|
||||||
let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer()
|
let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer()
|
||||||
tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:)))
|
tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:)))
|
||||||
|
@ -265,7 +262,7 @@ extension MainTabBarController {
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
wizard.consume()
|
isReadyForWizardAvatarButton = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
@ -377,51 +374,57 @@ extension MainTabBarController: UITabBarControllerDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - WizardDataSource
|
// MARK: - WizardViewControllerDelegate
|
||||||
extension MainTabBarController: WizardDelegate {
|
extension MainTabBarController: WizardViewControllerDelegate {
|
||||||
func spotlight(item: Wizard.Item) -> UIBezierPath {
|
func readyToLayoutItem(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> Bool {
|
||||||
switch item {
|
switch item {
|
||||||
case .multipleAccountSwitch:
|
case .multipleAccountSwitch:
|
||||||
guard let avatarButtonFrameInView = avatarButtonFrameInView() else {
|
return isReadyForWizardAvatarButton
|
||||||
return UIBezierPath()
|
|
||||||
}
|
|
||||||
return UIBezierPath(ovalIn: avatarButtonFrameInView)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func layoutWizardCard(_ wizard: MainTabBarController.Wizard, item: Wizard.Item) {
|
func layoutSpotlight(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> UIBezierPath {
|
||||||
switch item {
|
switch item {
|
||||||
case .multipleAccountSwitch:
|
case .multipleAccountSwitch:
|
||||||
guard let avatarButtonFrameInView = avatarButtonFrameInView() else {
|
guard let avatarButtonFrameInView = avatarButtonFrameInWizardView(wizardView: wizardViewController.view) else {
|
||||||
|
return UIBezierPath()
|
||||||
|
}
|
||||||
|
return UIBezierPath(ovalIn: avatarButtonFrameInView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func layoutWizardCard(_ wizardViewController: WizardViewController, item: WizardViewController.Item) {
|
||||||
|
switch item {
|
||||||
|
case .multipleAccountSwitch:
|
||||||
|
guard let avatarButtonFrameInView = avatarButtonFrameInWizardView(wizardView: wizardViewController.view) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let anchorView = UIView()
|
let anchorView = UIView()
|
||||||
anchorView.frame = avatarButtonFrameInView
|
anchorView.frame = avatarButtonFrameInView
|
||||||
wizard.backgroundView.addSubview(anchorView)
|
wizardViewController.backgroundView.addSubview(anchorView)
|
||||||
|
|
||||||
let wizardCardView = WizardCardView()
|
let wizardCardView = WizardCardView()
|
||||||
wizardCardView.arrowRectCorner = view.traitCollection.layoutDirection == .leftToRight ? .bottomRight : .bottomLeft
|
wizardCardView.arrowRectCorner = view.traitCollection.layoutDirection == .leftToRight ? .bottomRight : .bottomLeft
|
||||||
wizardCardView.titleLabel.text = item.title
|
wizardCardView.titleLabel.text = item.title
|
||||||
wizardCardView.descriptionLabel.text = item.description
|
wizardCardView.descriptionLabel.text = item.description
|
||||||
|
|
||||||
wizardCardView.translatesAutoresizingMaskIntoConstraints = false
|
wizardCardView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
wizard.backgroundView.addSubview(wizardCardView)
|
wizardViewController.backgroundView.addSubview(wizardCardView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
anchorView.topAnchor.constraint(equalTo: wizardCardView.bottomAnchor, constant: 13), // 13pt spacing
|
anchorView.topAnchor.constraint(equalTo: wizardCardView.bottomAnchor, constant: 13), // 13pt spacing
|
||||||
wizardCardView.trailingAnchor.constraint(equalTo: anchorView.centerXAnchor),
|
wizardCardView.trailingAnchor.constraint(equalTo: anchorView.centerXAnchor),
|
||||||
wizardCardView.widthAnchor.constraint(equalTo: wizard.backgroundView.widthAnchor, multiplier: 2.0/3.0).priority(.required - 1),
|
wizardCardView.widthAnchor.constraint(equalTo: wizardViewController.view.widthAnchor, multiplier: 2.0/3.0).priority(.required - 1),
|
||||||
])
|
])
|
||||||
wizardCardView.setContentHuggingPriority(.defaultLow, for: .vertical)
|
wizardCardView.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func avatarButtonFrameInView() -> CGRect? {
|
private func avatarButtonFrameInWizardView(wizardView: UIView) -> CGRect? {
|
||||||
guard let superview = avatarButton.superview else {
|
guard let superview = avatarButton.superview else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return superview.convert(avatarButton.frame, to: view)
|
return superview.convert(avatarButton.frame, to: wizardView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -241,3 +241,90 @@ extension RootSplitViewController: UISplitViewControllerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - WizardViewControllerDelegate
|
||||||
|
extension RootSplitViewController: WizardViewControllerDelegate {
|
||||||
|
|
||||||
|
func readyToLayoutItem(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> Bool {
|
||||||
|
guard traitCollection.horizontalSizeClass != .compact else {
|
||||||
|
return compactMainTabBarViewController.readyToLayoutItem(wizardViewController, item: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case .multipleAccountSwitch:
|
||||||
|
return contentSplitViewController.sidebarViewController.viewModel.isReadyForWizardAvatarButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func layoutSpotlight(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> UIBezierPath {
|
||||||
|
guard traitCollection.horizontalSizeClass != .compact else {
|
||||||
|
return compactMainTabBarViewController.layoutSpotlight(wizardViewController, item: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case .multipleAccountSwitch:
|
||||||
|
guard let frame = avatarButtonFrameInWizardView(wizardView: wizardViewController.view)
|
||||||
|
else {
|
||||||
|
assertionFailure()
|
||||||
|
return UIBezierPath()
|
||||||
|
}
|
||||||
|
return UIBezierPath(ovalIn: frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func layoutWizardCard(_ wizardViewController: WizardViewController, item: WizardViewController.Item) {
|
||||||
|
guard traitCollection.horizontalSizeClass != .compact else {
|
||||||
|
return compactMainTabBarViewController.layoutWizardCard(wizardViewController, item: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let frame = avatarButtonFrameInWizardView(wizardView: wizardViewController.view) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let anchorView = UIView()
|
||||||
|
anchorView.frame = frame
|
||||||
|
wizardViewController.backgroundView.addSubview(anchorView)
|
||||||
|
|
||||||
|
let wizardCardView = WizardCardView()
|
||||||
|
wizardCardView.arrowRectCorner = .allCorners // no arrow
|
||||||
|
wizardCardView.titleLabel.text = item.title
|
||||||
|
wizardCardView.descriptionLabel.text = item.description
|
||||||
|
|
||||||
|
wizardCardView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
wizardViewController.backgroundView.addSubview(wizardCardView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
wizardCardView.centerYAnchor.constraint(equalTo: anchorView.centerYAnchor),
|
||||||
|
wizardCardView.leadingAnchor.constraint(equalTo: anchorView.trailingAnchor, constant: 20), // 20pt spacing
|
||||||
|
wizardCardView.widthAnchor.constraint(equalToConstant: 320),
|
||||||
|
])
|
||||||
|
wizardCardView.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func avatarButtonFrameInWizardView(wizardView: UIView) -> CGRect? {
|
||||||
|
guard let diffableDataSource = contentSplitViewController.sidebarViewController.viewModel.diffableDataSource,
|
||||||
|
let indexPath = diffableDataSource.indexPath(for: .tab(.me)),
|
||||||
|
let cell = contentSplitViewController.sidebarViewController.collectionView.cellForItem(at: indexPath) as? SidebarListCollectionViewCell,
|
||||||
|
let contentView = cell._contentView,
|
||||||
|
let frame = sourceViewFrameInTargetView(
|
||||||
|
sourceView: contentView.avatarButton,
|
||||||
|
targetView: wizardView
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
assertionFailure()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sourceViewFrameInTargetView(
|
||||||
|
sourceView: UIView,
|
||||||
|
targetView: UIView
|
||||||
|
) -> CGRect? {
|
||||||
|
guard let superview = sourceView.superview else {
|
||||||
|
assertionFailure()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return superview.convert(sourceView.frame, to: targetView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -125,6 +125,7 @@ extension SidebarViewController {
|
||||||
|
|
||||||
secondaryCollectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] secondaryCollectionView, _ in
|
secondaryCollectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] secondaryCollectionView, _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): secondaryCollectionView contentSize: \(secondaryCollectionView.contentSize.debugDescription)")
|
||||||
let height = secondaryCollectionView.contentSize.height
|
let height = secondaryCollectionView.contentSize.height
|
||||||
self.secondaryCollectionViewHeightLayoutConstraint.constant = height
|
self.secondaryCollectionViewHeightLayoutConstraint.constant = height
|
||||||
self.collectionView.contentInset.bottom = height
|
self.collectionView.contentInset.bottom = height
|
||||||
|
|
|
@ -23,6 +23,7 @@ final class SidebarViewModel {
|
||||||
// output
|
// output
|
||||||
var diffableDataSource: UICollectionViewDiffableDataSource<Section, Item>?
|
var diffableDataSource: UICollectionViewDiffableDataSource<Section, Item>?
|
||||||
var secondaryDiffableDataSource: UICollectionViewDiffableDataSource<Section, Item>?
|
var secondaryDiffableDataSource: UICollectionViewDiffableDataSource<Section, Item>?
|
||||||
|
private(set) var isReadyForWizardAvatarButton = false
|
||||||
|
|
||||||
let activeMastodonAuthenticationObjectID = CurrentValueSubject<NSManagedObjectID?, Never>(nil)
|
let activeMastodonAuthenticationObjectID = CurrentValueSubject<NSManagedObjectID?, Never>(nil)
|
||||||
|
|
||||||
|
@ -170,8 +171,12 @@ extension SidebarViewModel {
|
||||||
.setting,
|
.setting,
|
||||||
]
|
]
|
||||||
sectionSnapshot.append(items, to: nil)
|
sectionSnapshot.append(items, to: nil)
|
||||||
_diffableDataSource.apply(sectionSnapshot, to: .main)
|
// animatingDifferences must to be `true`
|
||||||
|
// otherwise the UI layout will infinity loop
|
||||||
|
_diffableDataSource.apply(sectionSnapshot, to: .main, animatingDifferences: true) { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.isReadyForWizardAvatarButton = true
|
||||||
|
}
|
||||||
|
|
||||||
// secondary
|
// secondary
|
||||||
let _secondaryDiffableDataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: secondaryCollectionView) { collectionView, indexPath, item in
|
let _secondaryDiffableDataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: secondaryCollectionView) { collectionView, indexPath, item in
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
//
|
||||||
|
// WizardViewController.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-11-2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
protocol WizardViewControllerDelegate: AnyObject {
|
||||||
|
func readyToLayoutItem(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> Bool
|
||||||
|
func layoutSpotlight(_ wizardViewController: WizardViewController, item: WizardViewController.Item) -> UIBezierPath
|
||||||
|
func layoutWizardCard(_ wizardViewController: WizardViewController, item: WizardViewController.Item)
|
||||||
|
}
|
||||||
|
|
||||||
|
class WizardViewController: UIViewController {
|
||||||
|
|
||||||
|
let logger = Logger(subsystem: "Wizard", category: "UI")
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
weak var delegate: WizardViewControllerDelegate?
|
||||||
|
|
||||||
|
private(set) var items: [Item] = {
|
||||||
|
var items: [Item] = []
|
||||||
|
if !UserDefaults.shared.didShowMultipleAccountSwitchWizard {
|
||||||
|
items.append(.multipleAccountSwitch)
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}()
|
||||||
|
|
||||||
|
let pendingItem = CurrentValueSubject<Item?, Never>(nil)
|
||||||
|
let currentItem = CurrentValueSubject<Item?, Never>(nil)
|
||||||
|
|
||||||
|
let backgroundView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WizardViewController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
let backgroundTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
|
backgroundTapGestureRecognizer.addTarget(self, action: #selector(WizardViewController.backgroundTapGestureRecognizerHandler(_:)))
|
||||||
|
backgroundView.addGestureRecognizer(backgroundTapGestureRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
// Create a timer to consume pending item
|
||||||
|
Timer.publish(every: 0.5, on: .main, in: .default)
|
||||||
|
.autoconnect()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard self.pendingItem.value != nil else { return }
|
||||||
|
self.consume()
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
consume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLayoutSubviews() {
|
||||||
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
invalidLayoutForCurrentItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
super.viewWillTransition(to: size, with: coordinator)
|
||||||
|
|
||||||
|
coordinator.animate { context in
|
||||||
|
|
||||||
|
} completion: { [weak self] context in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.invalidLayoutForCurrentItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WizardViewController {
|
||||||
|
enum Item {
|
||||||
|
case multipleAccountSwitch
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
return L10n.Scene.Wizard.newInMastodon
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .multipleAccountSwitch:
|
||||||
|
return L10n.Scene.Wizard.multipleAccountSwitchIntroDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markAsRead() {
|
||||||
|
switch self {
|
||||||
|
case .multipleAccountSwitch:
|
||||||
|
UserDefaults.shared.didShowMultipleAccountSwitchWizard = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WizardViewController {
|
||||||
|
|
||||||
|
func setup() {
|
||||||
|
assert(delegate != nil, "need set delegate before use")
|
||||||
|
|
||||||
|
guard !items.isEmpty else { return }
|
||||||
|
|
||||||
|
backgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
backgroundView.frame = view.bounds
|
||||||
|
view.addSubview(backgroundView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func destroy() {
|
||||||
|
view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
func consume() {
|
||||||
|
guard !items.isEmpty else {
|
||||||
|
destroy()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let first = items.first else { return }
|
||||||
|
guard delegate?.readyToLayoutItem(self, item: first) == true else {
|
||||||
|
pendingItem.value = first
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pendingItem.value = nil
|
||||||
|
currentItem.value = nil
|
||||||
|
|
||||||
|
let item = items.removeFirst()
|
||||||
|
perform(item: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func perform(item: Item) {
|
||||||
|
guard let delegate = delegate else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare for reuse
|
||||||
|
prepareForReuse()
|
||||||
|
|
||||||
|
// set wizard item read
|
||||||
|
item.markAsRead()
|
||||||
|
|
||||||
|
// add spotlight
|
||||||
|
let spotlight = delegate.layoutSpotlight(self, item: item)
|
||||||
|
let maskLayer = CAShapeLayer()
|
||||||
|
// expand rect to make sure view always fill the screen when device rotate
|
||||||
|
let expandRect: CGRect = {
|
||||||
|
var rect = backgroundView.bounds
|
||||||
|
rect.size.width *= 2
|
||||||
|
rect.size.height *= 2
|
||||||
|
return rect
|
||||||
|
}()
|
||||||
|
let path = UIBezierPath(rect: expandRect)
|
||||||
|
path.append(spotlight)
|
||||||
|
maskLayer.fillRule = .evenOdd
|
||||||
|
maskLayer.path = path.cgPath
|
||||||
|
backgroundView.layer.mask = maskLayer
|
||||||
|
|
||||||
|
// layout wizard card
|
||||||
|
delegate.layoutWizardCard(self, item: item)
|
||||||
|
|
||||||
|
currentItem.value = item
|
||||||
|
}
|
||||||
|
|
||||||
|
private func prepareForReuse() {
|
||||||
|
backgroundView.subviews.forEach { subview in
|
||||||
|
subview.removeFromSuperview()
|
||||||
|
}
|
||||||
|
backgroundView.mask = nil
|
||||||
|
backgroundView.layer.mask = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func invalidLayoutForCurrentItem() {
|
||||||
|
if let item = currentItem.value {
|
||||||
|
perform(item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WizardViewController {
|
||||||
|
@objc private func backgroundTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
||||||
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
|
|
||||||
|
consume()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue