mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
chore: inject AuthContext
This commit is contained in:
parent
db86bce8cf
commit
f73241caee
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -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 }}
|
||||
|
@ -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
|
||||
|
@ -410,7 +410,6 @@ extension HomeTimelineViewController {
|
||||
Task { @MainActor in
|
||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: authenticationBox)
|
||||
self.coordinator.setup()
|
||||
self.coordinator.setupOnboardingIfNeeds(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -299,7 +299,6 @@ extension SettingsViewController {
|
||||
Task { @MainActor in
|
||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: authenticationBox)
|
||||
self.coordinator.setup()
|
||||
self.coordinator.setupOnboardingIfNeeds(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
self.coordinator = sceneCoordinator
|
||||
|
||||
sceneCoordinator.setup()
|
||||
sceneCoordinator.setupOnboardingIfNeeds(animated: false)
|
||||
window.makeKeyAndVisible()
|
||||
|
||||
#if SNAPSHOT
|
||||
|
@ -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
|
||||
|
64
MastodonSDK/Sources/MastodonCore/AuthContext.swift
Normal file
64
MastodonSDK/Sources/MastodonCore/AuthContext.swift
Normal 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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user