chore: inject AuthContext

This commit is contained in:
CMK 2022-10-08 15:16:10 +08:00
parent db86bce8cf
commit f73241caee
12 changed files with 137 additions and 48 deletions

View File

@ -15,12 +15,10 @@ on:
jobs:
build:
name: CI build
runs-on: macos-11
runs-on: macos-12
steps:
- name: checkout
uses: actions/checkout@v2
- name: force Xcode 13.2.1
run: sudo xcode-select -switch /Applications/Xcode_13.2.1.app
- name: setup
env:
NotificationEndpointDebug: ${{ secrets.NotificationEndpointDebug }}

View File

@ -22,6 +22,8 @@ final public class SceneCoordinator {
private weak var sceneDelegate: SceneDelegate!
private weak var appContext: AppContext!
private var authContext: AuthContext?
let id = UUID().uuidString
private(set) weak var tabBarController: MainTabBarController!
@ -30,7 +32,11 @@ final public class SceneCoordinator {
private(set) var secondaryStackHashValues = Set<Int>()
init(scene: UIScene, sceneDelegate: SceneDelegate, appContext: AppContext) {
init(
scene: UIScene,
sceneDelegate: SceneDelegate,
appContext: AppContext
) {
self.scene = scene
self.sceneDelegate = sceneDelegate
self.appContext = appContext
@ -225,55 +231,48 @@ extension SceneCoordinator {
func setup() {
let rootViewController: UIViewController
switch UIDevice.current.userInterfaceIdiom {
case .phone:
let viewController = MainTabBarController(context: appContext, coordinator: self)
self.splitViewController = nil
self.tabBarController = viewController
rootViewController = viewController
default:
let splitViewController = RootSplitViewController(context: appContext, coordinator: self)
self.splitViewController = splitViewController
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
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) {
// Check user authentication status and show onboarding if needs
do {
let request = MastodonAuthentication.sortedFetchRequest
if try appContext.managedObjectContext.count(for: request) == 0 {
let _authentication = try appContext.managedObjectContext.fetch(request).first
let _authContext = _authentication.flatMap { AuthContext(authentication: $0) }
self.authContext = _authContext
switch UIDevice.current.userInterfaceIdiom {
case .phone:
let viewController = MainTabBarController(context: appContext, coordinator: self, authContext: _authContext)
self.splitViewController = nil
self.tabBarController = viewController
rootViewController = viewController
default:
let splitViewController = RootSplitViewController(context: appContext, coordinator: self, authContext: _authContext)
self.splitViewController = splitViewController
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
rootViewController = splitViewController
}
sceneDelegate.window?.rootViewController = rootViewController // base: main
if _authContext == nil { // entry #1: welcome
DispatchQueue.main.async {
self.present(
_ = self.present(
scene: .welcome,
from: self.sceneDelegate.window?.rootViewController,
transition: .modal(animated: animated, completion: nil)
transition: .modal(animated: true, completion: nil)
)
}
}
} catch {
assertionFailure(error.localizedDescription)
Task {
try? await Task.sleep(nanoseconds: .second * 2)
setup() // entry #2: retry
} // end Task
}
}
@discardableResult
@MainActor
@discardableResult
func present(scene: Scene, from sender: UIViewController?, transition: Transition) -> UIViewController? {
guard let viewController = get(scene: scene) else {
return nil

View File

@ -410,7 +410,6 @@ extension HomeTimelineViewController {
Task { @MainActor in
try await context.authenticationService.signOutMastodonUser(authenticationBox: authenticationBox)
self.coordinator.setup()
self.coordinator.setupOnboardingIfNeeds(animated: true)
}
}

View File

@ -24,6 +24,8 @@ final class ContentSplitViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var authContext: AuthContext?
weak var delegate: ContentSplitViewControllerDelegate?
private(set) lazy var sidebarViewController: SidebarViewController = {
@ -37,7 +39,7 @@ final class ContentSplitViewController: UIViewController, NeedsDependency {
@Published var currentSupplementaryTab: MainTabBarController.Tab = .home
private(set) lazy var mainTabBarController: MainTabBarController = {
let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator)
let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator, authContext: authContext)
if let homeTimelineViewController = mainTabBarController.viewController(of: HomeTimelineViewController.self) {
homeTimelineViewController.viewModel.displaySettingBarButtonItem = false
}

View File

@ -23,6 +23,8 @@ class MainTabBarController: UITabBarController {
weak var context: AppContext!
weak var coordinator: SceneCoordinator!
var authContext: AuthContext?
let composeButttonShadowBackgroundContainer = ShadowBackgroundContainer()
let composeButton: UIButton = {
let button = UIButton()
@ -143,9 +145,14 @@ class MainTabBarController: UITabBarController {
var avatarURLObserver: AnyCancellable?
@Published var avatarURL: URL?
init(context: AppContext, coordinator: SceneCoordinator) {
init(
context: AppContext,
coordinator: SceneCoordinator,
authContext: AuthContext?
) {
self.context = context
self.coordinator = coordinator
self.authContext = authContext
super.init(nibName: nil, bundle: nil)
}

View File

@ -20,12 +20,15 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var authContext: AuthContext?
private var isPrimaryDisplay = false
private(set) lazy var contentSplitViewController: ContentSplitViewController = {
let contentSplitViewController = ContentSplitViewController()
contentSplitViewController.context = context
contentSplitViewController.coordinator = coordinator
contentSplitViewController.authContext = authContext
contentSplitViewController.delegate = self
return contentSplitViewController
}()
@ -37,13 +40,14 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency {
return searchViewController
}()
lazy var compactMainTabBarViewController = MainTabBarController(context: context, coordinator: coordinator)
lazy var compactMainTabBarViewController = MainTabBarController(context: context, coordinator: coordinator, authContext: authContext)
let separatorLine = UIView.separatorLine
init(context: AppContext, coordinator: SceneCoordinator) {
init(context: AppContext, coordinator: SceneCoordinator, authContext: AuthContext?) {
self.context = context
self.coordinator = coordinator
self.authContext = authContext
super.init(style: .doubleColumn)
primaryEdge = .trailing

View File

@ -299,7 +299,6 @@ extension SettingsViewController {
Task { @MainActor in
try await context.authenticationService.signOutMastodonUser(authenticationBox: authenticationBox)
self.coordinator.setup()
self.coordinator.setupOnboardingIfNeeds(animated: true)
}
}

View File

@ -58,7 +58,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
self.coordinator = sceneCoordinator
sceneCoordinator.setup()
sceneCoordinator.setupOnboardingIfNeeds(animated: false)
window.makeKeyAndVisible()
#if SNAPSHOT

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

View File

@ -0,0 +1,64 @@
//
// AuthContext.swift
//
//
// Created by MainasuK on 22/10/8.
//
import os.log
import Foundation
import Combine
import CoreDataStack
import MastodonSDK
public protocol AuthContextProvider {
var authContext: AuthContext { get }
}
public class AuthContext {
var disposeBag = Set<AnyCancellable>()
let logger = Logger(subsystem: "AuthContext", category: "AuthContext")
// Mastodon
public private(set) var mastodonAuthenticationBox: MastodonAuthenticationBox
private init(mastodonAuthenticationBox: MastodonAuthenticationBox) {
self.mastodonAuthenticationBox = mastodonAuthenticationBox
}
}
extension AuthContext {
public convenience init?(authentication: MastodonAuthentication) {
self.init(mastodonAuthenticationBox: MastodonAuthenticationBox(authentication: authentication))
ManagedObjectObserver.observe(object: authentication)
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
switch completion {
case .failure(let error):
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(error.localizedDescription)")
case .finished:
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): observer finished")
}
} receiveValue: { [weak self] change in
guard let self = self else { return }
switch change.changeType {
case .update(let object):
guard let authentication = object as? MastodonAuthentication else {
assertionFailure()
return
}
self.mastodonAuthenticationBox = .init(authentication: authentication)
default:
break
}
}
.store(in: &disposeBag)
}
}

View File

@ -30,3 +30,17 @@ public struct MastodonAuthenticationBox: UserIdentifier {
self.userAuthorization = userAuthorization
}
}
extension MastodonAuthenticationBox {
init(authentication: MastodonAuthentication) {
self = MastodonAuthenticationBox(
authenticationRecord: .init(objectID: authentication.objectID),
domain: authentication.domain,
userID: authentication.userID,
appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken),
userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken)
)
}
}

View File

@ -13,11 +13,15 @@ extension Mastodon.API.OAuth {
public static let authorizationField = "Authorization"
public struct Authorization {
public let accessToken: String
public private(set) var accessToken: String
public init(accessToken: String) {
self.accessToken = accessToken
}
public mutating func update(accessToken: String) {
self.accessToken = accessToken
}
}
}