Refactor: Remove NeedsDependency

The AppContext is already a singleton. SceneCoordinators are unique to UIWindowScenes, so fetch them that way.

Fixes iOS-324
This commit is contained in:
shannon 2024-12-02 14:45:15 -05:00
parent fc7ebd65f8
commit 65bf555d98
142 changed files with 401 additions and 757 deletions

View File

@ -397,7 +397,6 @@
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */; };
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */; };
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54225C13647002E6C99 /* SceneCoordinator.swift */; };
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54325C13647002E6C99 /* NeedsDependency.swift */; };
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */; };
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; };
DB8F7076279E954700E1225B /* DataSourceFacade+Follow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8F7075279E954700E1225B /* DataSourceFacade+Follow.swift */; };
@ -1100,7 +1099,6 @@
DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionAppendEntryCollectionViewCell.swift; sourceTree = "<group>"; };
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mastodon.entitlements; sourceTree = "<group>"; };
DB8AF54225C13647002E6C99 /* SceneCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneCoordinator.swift; sourceTree = "<group>"; };
DB8AF54325C13647002E6C99 /* NeedsDependency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeedsDependency.swift; sourceTree = "<group>"; };
DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = "<group>"; };
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
DB8D8E3128196FA0009FD90F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = "<group>"; };
@ -2588,7 +2586,6 @@
isa = PBXGroup;
children = (
DB8AF54225C13647002E6C99 /* SceneCoordinator.swift */,
DB8AF54325C13647002E6C99 /* NeedsDependency.swift */,
D8F9170E2A4B47EF008A5370 /* Coordinator.swift */,
);
path = Coordinator;
@ -3812,7 +3809,6 @@
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */,
DBEFCD7B282A162400C0ABEA /* ReportReasonView.swift in Sources */,
D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */,
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
DB63F77B279ACAE500455B82 /* DataSourceFacade+Favorite.swift in Sources */,
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */,

View File

@ -15,7 +15,7 @@ final class SafariActivity: UIActivity {
weak var sceneCoordinator: SceneCoordinator?
var url: NSURL?
init(sceneCoordinator: SceneCoordinator) {
init(sceneCoordinator: SceneCoordinator?) {
self.sceneCoordinator = sceneCoordinator
}

View File

@ -1,19 +0,0 @@
//
// NeedsDependency.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import UIKit
import MastodonCore
protocol NeedsDependency: AnyObject {
//FIXME: Get rid of ! ~@zeitschlag
var context: AppContext! { get set }
var coordinator: SceneCoordinator! { get set }
}
typealias ViewControllerWithDependencies = NeedsDependency & UIViewController

View File

@ -17,11 +17,14 @@ import MBProgressHUD
@MainActor
final public class SceneCoordinator {
fileprivate static func coordinator(for view: UIView) -> SceneCoordinator? {
return SceneDelegate.delegate(for: view)?.coordinator
}
private var disposeBag = Set<AnyCancellable>()
private weak var scene: UIScene!
private weak var sceneDelegate: SceneDelegate!
private(set) weak var appContext: AppContext!
var authenticationBox: MastodonAuthenticationBox? {
AuthenticationServiceProvider.shared.currentActiveUser.value
@ -45,7 +48,6 @@ final public class SceneCoordinator {
) {
self.scene = scene
self.sceneDelegate = sceneDelegate
self.appContext = appContext
NotificationService.shared.requestRevealNotificationPublisher
.receive(on: DispatchQueue.main)
@ -118,7 +120,6 @@ final public class SceneCoordinator {
break
case .mention, .reblog, .favourite, .poll, .status:
let threadViewModel = RemoteThreadViewModel(
context: appContext,
authenticationBox: authenticationBox,
notificationID: notificationID
)
@ -250,17 +251,18 @@ extension SceneCoordinator {
switch UIDevice.current.userInterfaceIdiom {
case .phone:
let viewController = MainTabBarController(context: appContext, coordinator: self, authenticationBox: authenticationBox)
let viewController = MainTabBarController(authenticationBox: authenticationBox)
self.splitViewController = nil
self.tabBarController = viewController
rootViewController = viewController
default:
let splitViewController = RootSplitViewController(context: appContext, coordinator: self, authenticationBox: authenticationBox)
let splitViewController = RootSplitViewController(authenticationBox: authenticationBox)
self.splitViewController = splitViewController
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
rootViewController = splitViewController
}
// this feels wrong
sceneDelegate.window?.rootViewController = rootViewController // base: main
self.rootViewController = rootViewController
@ -268,7 +270,7 @@ extension SceneCoordinator {
DispatchQueue.main.async {
_ = self.present(
scene: .welcome,
from: self.sceneDelegate.window?.rootViewController,
from: rootViewController, // self.sceneDelegate.window?.rootViewController,
transition: .modal(animated: true, completion: nil)
)
}
@ -422,13 +424,11 @@ private extension SceneCoordinator {
let _viewController = WebViewController(viewModel)
viewController = _viewController
case .searchDetail(let viewModel):
let _viewController = SearchDetailViewController(appContext: appContext, sceneCoordinator: self, authenticationBox: viewModel.authenticationBox)
let _viewController = SearchDetailViewController(authenticationBox: viewModel.authenticationBox)
_viewController.viewModel = viewModel
viewController = _viewController
case .searchResult(let viewModel):
let searchResultViewController = SearchResultViewController()
searchResultViewController.context = appContext
searchResultViewController.coordinator = self
searchResultViewController.viewModel = viewModel
viewController = searchResultViewController
case .compose(let viewModel):
@ -460,19 +460,19 @@ private extension SceneCoordinator {
case .followedTags(let viewModel):
guard let authenticationBox else { return nil }
viewController = FollowedTagsViewController(appContext: appContext, sceneCoordinator: self, authenticationBox: authenticationBox, viewModel: viewModel)
viewController = FollowedTagsViewController(authenticationBox: authenticationBox, viewModel: viewModel)
case .favorite(let viewModel):
let _viewController = FavoriteViewController()
_viewController.viewModel = viewModel
viewController = _viewController
case .follower(let viewModel):
let followerListViewController = FollowerListViewController(viewModel: viewModel, coordinator: self, context: appContext)
let followerListViewController = FollowerListViewController(viewModel: viewModel)
viewController = followerListViewController
case .following(let viewModel):
let followingListViewController = FollowingListViewController(viewModel: viewModel, coordinator: self, context: appContext)
let followingListViewController = FollowingListViewController(viewModel: viewModel)
viewController = followingListViewController
case .familiarFollowers(let viewModel):
viewController = FamiliarFollowersViewController(viewModel: viewModel, context: appContext, coordinator: self)
viewController = FamiliarFollowersViewController(viewModel: viewModel)
case .rebloggedBy(let viewModel):
let _viewController = RebloggedByViewController()
_viewController.viewModel = viewModel
@ -539,7 +539,7 @@ private extension SceneCoordinator {
let settingsCoordinator = SettingsCoordinator(presentedOn: presentedOn,
accountName: accountName,
setting: setting,
appContext: appContext,
appContext: AppContext.shared,
authenticationBox: authenticationBox,
sceneCoordinator: self
)
@ -557,18 +557,11 @@ private extension SceneCoordinator {
case .notificationPolicy(let viewModel):
viewController = NotificationPolicyViewController(viewModel: viewModel)
case .accountNotificationTimeline(let viewModel, let request):
viewController = AccountNotificationTimelineViewController(viewModel: viewModel, context: appContext, coordinator: self, notificationRequest: request)
viewController = AccountNotificationTimelineViewController(viewModel: viewModel, notificationRequest: request)
}
setupDependency(for: viewController as? NeedsDependency)
return viewController
}
private func setupDependency(for needs: NeedsDependency?) {
needs?.context = appContext
needs?.coordinator = self
}
}
//MARK: - Loading
@ -692,3 +685,10 @@ extension SceneCoordinator: SettingsCoordinatorDelegate {
self.mastodonAuthenticationController = authenticationController
}
}
public extension UIViewController {
var sceneCoordinator: SceneCoordinator? {
guard let view = viewIfLoaded else { assert(false); return nil }
return SceneCoordinator.coordinator(for: view)
}
}

View File

@ -47,7 +47,6 @@ extension ReportSection {
case .status(let status):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ReportStatusTableViewCell.self), for: indexPath) as! ReportStatusTableViewCell
configure(
context: context,
tableView: tableView,
cell: cell,
viewModel: .init(value: status),
@ -83,19 +82,16 @@ extension ReportSection {
extension ReportSection {
static func configure(
context: AppContext,
tableView: UITableView,
cell: ReportStatusTableViewCell,
viewModel: ReportStatusTableViewCell.ViewModel,
configuration: Configuration
) {
StatusSection.setupStatusPollDataSource(
context: context,
authenticationBox: configuration.authenticationBox,
statusView: cell.statusView
)
cell.statusView.viewModel.context = context
cell.statusView.viewModel.authenticationBox = configuration.authenticationBox
cell.configure(

View File

@ -24,7 +24,6 @@ enum StatusSection: Equatable, Hashable {
extension StatusSection {
struct Configuration {
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
weak var statusTableViewCellDelegate: StatusTableViewCellDelegate?
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
@ -33,7 +32,6 @@ extension StatusSection {
static func diffableDataSource(
tableView: UITableView,
context: AppContext,
configuration: Configuration
) -> UITableViewDiffableDataSource<StatusSection, StatusItem> {
tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self))
@ -48,7 +46,6 @@ extension StatusSection {
let displayItem = StatusTableViewCell.StatusTableViewCellViewModel.DisplayItem.feed(feed)
let contentConcealModel = StatusView.ContentConcealViewModel(status: feed.status, filterBox: StatusFilterService.shared.activeFilterBox, filterContext: configuration.filterContext)
configure(
context: context,
tableView: tableView,
cell: cell,
viewModel: StatusTableViewCell.StatusTableViewCellViewModel(displayItem: displayItem, contentConcealModel: contentConcealModel),
@ -68,7 +65,6 @@ extension StatusSection {
let displayItem = StatusTableViewCell.StatusTableViewCellViewModel.DisplayItem.status(status)
let contentConcealModel = StatusView.ContentConcealViewModel(status: status, filterBox: StatusFilterService.shared.activeFilterBox, filterContext: configuration.filterContext)
configure(
context: context,
tableView: tableView,
cell: cell,
viewModel: StatusTableViewCell.StatusTableViewCellViewModel(displayItem: displayItem, contentConcealModel: contentConcealModel),
@ -77,7 +73,6 @@ extension StatusSection {
return cell
case .thread(let thread):
let cell = dequeueConfiguredReusableCell(
context: context,
tableView: tableView,
indexPath: indexPath,
configuration: ThreadCellRegistrationConfiguration(
@ -108,7 +103,6 @@ extension StatusSection {
}
static func dequeueConfiguredReusableCell(
context: AppContext,
tableView: UITableView,
indexPath: IndexPath,
configuration: ThreadCellRegistrationConfiguration
@ -118,7 +112,6 @@ extension StatusSection {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusThreadRootTableViewCell.self), for: indexPath) as! StatusThreadRootTableViewCell
let contentConcealModel = StatusView.ContentConcealViewModel(status: threadContext.status, filterBox: StatusFilterService.shared.activeFilterBox, filterContext: .thread)
StatusSection.configure(
context: context,
tableView: tableView,
cell: cell,
viewModel: StatusTableViewCell.StatusTableViewCellViewModel(displayItem: .status(threadContext.status), contentConcealModel: contentConcealModel),
@ -132,9 +125,7 @@ extension StatusSection {
let contentConcealModel = StatusView.ContentConcealViewModel(status: threadContext.status, filterBox: StatusFilterService.shared.activeFilterBox, filterContext: configuration.configuration.filterContext)
assert(configuration.configuration.filterContext == .thread)
StatusSection.configure(
context: context,
tableView: tableView,
cell: cell,
tableView: tableView, cell: cell,
viewModel: StatusTableViewCell.StatusTableViewCellViewModel(displayItem: displayItem, contentConcealModel: contentConcealModel),
configuration: configuration.configuration
)
@ -147,7 +138,6 @@ extension StatusSection {
extension StatusSection {
public static func setupStatusPollDataSource(
context: AppContext,
authenticationBox: MastodonAuthenticationBox,
statusView: StatusView
) {
@ -206,19 +196,16 @@ extension StatusSection {
extension StatusSection {
static func configure(
context: AppContext,
tableView: UITableView,
cell: StatusTableViewCell,
viewModel: StatusTableViewCell.StatusTableViewCellViewModel,
configuration: Configuration
) {
setupStatusPollDataSource(
context: context,
authenticationBox: configuration.authenticationBox,
statusView: cell.statusView
)
cell.statusView.viewModel.context = configuration.context
cell.statusView.viewModel.authenticationBox = configuration.authenticationBox
cell.configure(
@ -229,19 +216,16 @@ extension StatusSection {
}
static func configure(
context: AppContext,
tableView: UITableView,
cell: StatusThreadRootTableViewCell,
viewModel: StatusTableViewCell.StatusTableViewCellViewModel,
configuration: Configuration
) {
setupStatusPollDataSource(
context: context,
authenticationBox: configuration.authenticationBox,
statusView: cell.statusView
)
cell.statusView.viewModel.context = configuration.context
cell.statusView.viewModel.authenticationBox = configuration.authenticationBox
cell.configure(

View File

@ -21,7 +21,6 @@ enum UserSection: Hashable {
extension UserSection {
static func diffableDataSource(
tableView: UITableView,
context: AppContext,
authenticationBox: MastodonAuthenticationBox,
userTableViewCellDelegate: UserTableViewCellDelegate?
) -> UITableViewDiffableDataSource<UserSection, UserItem> {

View File

@ -12,7 +12,7 @@ import MastodonSDK
extension DataSourceFacade {
static func responseToUserBlockAction(
dependency: NeedsDependency & AuthContextProvider,
dependency: AuthContextProvider,
account: Mastodon.Entity.Account
) async throws -> Mastodon.Entity.Relationship {
FeedbackGenerator.shared.generate(.selectionChanged)
@ -35,7 +35,7 @@ extension DataSourceFacade {
}
static func responseToDomainBlockAction(
dependency: NeedsDependency & AuthContextProvider,
dependency: AuthContextProvider,
account: Mastodon.Entity.Account
) async throws -> Mastodon.Entity.Empty {
FeedbackGenerator.shared.generate(.selectionChanged)

View File

@ -14,7 +14,7 @@ import MastodonSDK
extension DataSourceFacade {
@MainActor
public static func responseToStatusBookmarkAction(
provider: NeedsDependency & AuthContextProvider & DataSourceProvider,
provider: AuthContextProvider & DataSourceProvider,
status: MastodonStatus
) async throws {
FeedbackGenerator.shared.generate(.selectionChanged)

View File

@ -14,7 +14,7 @@ import MastodonLocalization
extension DataSourceFacade {
@MainActor
static func responseToUserFollowAction(
dependency: ViewControllerWithDependencies & AuthContextProvider,
dependency: UIViewController & AuthContextProvider,
account: Mastodon.Entity.Account
) async throws -> Mastodon.Entity.Relationship {
let authBox = dependency.authenticationBox
@ -78,7 +78,7 @@ extension DataSourceFacade {
extension DataSourceFacade {
static func responseToUserFollowRequestAction(
dependency: NeedsDependency & AuthContextProvider,
dependency: UIViewController & AuthContextProvider,
notification: MastodonNotification,
notificationView: NotificationView,
query: Mastodon.API.Account.FollowRequestQuery
@ -129,10 +129,11 @@ extension DataSourceFacade {
case .notFound:
break
default:
guard let coordinator = await dependency.sceneCoordinator else { return }
let alertController = await UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = await UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
await alertController.addAction(okAction)
_ = await dependency.coordinator.present(
_ = await coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
@ -146,7 +147,7 @@ extension DataSourceFacade {
extension DataSourceFacade {
static func responseToShowHideReblogAction(
dependency: NeedsDependency & AuthContextProvider,
dependency: AuthContextProvider,
account: Mastodon.Entity.Account
) async throws {
let newRelationship = try await APIService.shared.toggleShowReblogs(

View File

@ -13,16 +13,16 @@ import MastodonSDK
extension DataSourceFacade {
@MainActor
static func coordinateToHashtagScene(
provider: ViewControllerWithDependencies & AuthContextProvider,
provider: UIViewController,
tag: Mastodon.Entity.Tag
) async {
guard let authBox = AuthenticationServiceProvider.shared.currentActiveUser.value else { return }
let hashtagTimelineViewModel = HashtagTimelineViewModel(
context: provider.context,
authenticationBox: provider.authenticationBox,
authenticationBox: authBox,
hashtag: tag.name
)
_ = provider.coordinator.present(
guard let coordinator = provider.sceneCoordinator else { return }
_ = coordinator.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: provider,
transition: .show

View File

@ -7,6 +7,7 @@
import UIKit
import CoreDataStack
import MastodonCore
import MastodonUI
import MastodonLocalization
import MastodonSDK
@ -15,16 +16,17 @@ extension DataSourceFacade {
@MainActor
static func coordinateToMediaPreviewScene(
dependency: NeedsDependency & MediaPreviewableViewController,
dependency: UIViewController & MediaPreviewableViewController,
mediaPreviewItem: MediaPreviewViewModel.PreviewItem,
mediaPreviewTransitionItem: MediaPreviewTransitionItem
) {
let mediaPreviewViewModel = MediaPreviewViewModel(
context: dependency.context,
context: AppContext.shared,
item: mediaPreviewItem,
transitionItem: mediaPreviewTransitionItem
)
_ = dependency.coordinator.present(
guard let coordinator = dependency.sceneCoordinator else { return }
_ = coordinator.present(
scene: .mediaPreview(viewModel: mediaPreviewViewModel),
from: dependency,
transition: .custom(transitioningDelegate: dependency.mediaPreviewTransitionController)
@ -61,7 +63,7 @@ extension DataSourceFacade {
@MainActor
static func coordinateToMediaPreviewScene(
dependency: NeedsDependency & MediaPreviewableViewController,
dependency: UIViewController & MediaPreviewableViewController,
status: MastodonStatus,
previewContext: AttachmentPreviewContext
) async throws {
@ -146,7 +148,7 @@ extension DataSourceFacade {
@MainActor
static func coordinateToMediaPreviewScene(
dependency: NeedsDependency & MediaPreviewableViewController,
dependency: MediaPreviewableViewController,
account: Mastodon.Entity.Account,
previewContext: ImagePreviewContext
) async throws {

View File

@ -53,8 +53,9 @@ extension DataSourceFacade {
url: url
)
case .hashtag(_, let hashtag, _):
let hashtagTimelineViewModel = await HashtagTimelineViewModel(context: provider.context, authenticationBox: provider.authenticationBox, hashtag: hashtag)
_ = await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show)
let hashtagTimelineViewModel = await HashtagTimelineViewModel(authenticationBox: provider.authenticationBox, hashtag: hashtag)
guard let coordinator = await provider.sceneCoordinator else { return }
_ = await coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show)
case .mention(_, let mention, let userInfo):
await coordinateToProfileScene(
provider: provider,

View File

@ -11,7 +11,7 @@ import MastodonCore
extension DataSourceFacade {
static func responseToUserMuteAction(
dependency: NeedsDependency & AuthContextProvider,
dependency: AuthContextProvider,
account: Mastodon.Entity.Account
) async throws -> Mastodon.Entity.Relationship {
FeedbackGenerator.shared.generate(.selectionChanged)

View File

@ -3,19 +3,21 @@
import Foundation
import MastodonCore
import MastodonSDK
import UIKit
extension DataSourceFacade {
@MainActor
static func coordinateToNotificationRequests(
provider: DataSourceProvider & AuthContextProvider
) async {
provider.coordinator.showLoading()
guard let sceneCoordinator = provider.sceneCoordinator else { return }
sceneCoordinator.showLoading()
do {
let notificationRequests = try await APIService.shared.notificationRequests(authenticationBox: provider.authenticationBox).value
let viewModel = NotificationRequestsViewModel(appContext: provider.context, authenticationBox: provider.authenticationBox, coordinator: provider.coordinator, requests: notificationRequests)
let viewModel = NotificationRequestsViewModel(authenticationBox: provider.authenticationBox, requests: notificationRequests)
provider.coordinator.hideLoading()
sceneCoordinator.hideLoading()
let transition: SceneCoordinator.Transition
@ -25,25 +27,26 @@ extension DataSourceFacade {
transition = .modal(animated: true)
}
provider.coordinator.present(scene: .notificationRequests(viewModel: viewModel), transition: transition)
sceneCoordinator.present(scene: .notificationRequests(viewModel: viewModel), transition: transition)
} catch {
//TODO: Error Handling
provider.coordinator.hideLoading()
sceneCoordinator.hideLoading()
}
}
@MainActor
static func coordinateToNotificationRequest(
request: Mastodon.Entity.NotificationRequest,
provider: ViewControllerWithDependencies & AuthContextProvider
provider: UIViewController & AuthContextProvider
) async -> AccountNotificationTimelineViewController? {
provider.coordinator.showLoading()
guard let sceneCoordinator = provider.sceneCoordinator else { return nil }
sceneCoordinator.showLoading()
let notificationTimelineViewModel = NotificationTimelineViewModel(context: provider.context, authenticationBox: provider.authenticationBox, scope: .fromAccount(request.account))
let notificationTimelineViewModel = NotificationTimelineViewModel(authenticationBox: provider.authenticationBox, scope: .fromAccount(request.account))
provider.coordinator.hideLoading()
sceneCoordinator.hideLoading()
guard let viewController = provider.coordinator.present(scene: .accountNotificationTimeline(viewModel: notificationTimelineViewModel, request: request), transition: .show) as? AccountNotificationTimelineViewController else { return nil }
guard let viewController = sceneCoordinator.present(scene: .accountNotificationTimeline(viewModel: notificationTimelineViewModel, request: request), transition: .show) as? AccountNotificationTimelineViewController else { return nil }
return viewController

View File

@ -26,7 +26,9 @@ extension DataSourceFacade {
acct = status.entity.account.acct
}
provider.coordinator.showLoading()
guard let coordinator = provider.sceneCoordinator else { return }
coordinator.showLoading()
let _redirectRecord = try? await Mastodon.API.Account.lookupAccount(
session: .shared,
@ -35,7 +37,7 @@ extension DataSourceFacade {
authorization: provider.authenticationBox.userAuthorization
).singleOutput().value
provider.coordinator.hideLoading()
coordinator.hideLoading()
guard let redirectRecord = _redirectRecord else {
assertionFailure()
@ -50,11 +52,13 @@ extension DataSourceFacade {
@MainActor
static func coordinateToProfileScene(
provider: ViewControllerWithDependencies & AuthContextProvider,
provider: UIViewController & AuthContextProvider,
username: String,
domain: String
) async {
provider.coordinator.showLoading()
guard let coordinator = provider.sceneCoordinator else { return }
coordinator.showLoading()
do {
guard let account = try await APIService.shared.fetchUser(
@ -62,24 +66,26 @@ extension DataSourceFacade {
domain: domain,
authenticationBox: provider.authenticationBox
) else {
return provider.coordinator.hideLoading()
return coordinator.hideLoading()
}
provider.coordinator.hideLoading()
coordinator.hideLoading()
await coordinateToProfileScene(provider: provider, account: account)
} catch {
provider.coordinator.hideLoading()
coordinator.hideLoading()
}
}
@MainActor
static func coordinateToProfileScene(
provider: ViewControllerWithDependencies & AuthContextProvider,
provider: UIViewController & AuthContextProvider,
domain: String,
accountID: String
) async {
provider.coordinator.showLoading()
guard let coordinator = provider.sceneCoordinator else { return }
coordinator.showLoading()
do {
let account = try await APIService.shared.accountInfo(
@ -89,37 +95,40 @@ extension DataSourceFacade {
authorization: provider.authenticationBox.userAuthorization
)
provider.coordinator.hideLoading()
coordinator.hideLoading()
await coordinateToProfileScene(provider: provider, account: account)
} catch {
provider.coordinator.hideLoading()
coordinator.hideLoading()
}
}
@MainActor
public static func coordinateToProfileScene(
provider: ViewControllerWithDependencies & AuthContextProvider,
provider: UIViewController & AuthContextProvider,
account: Mastodon.Entity.Account
) async {
provider.coordinator.showLoading()
guard let coordinator = provider.sceneCoordinator else { return }
coordinator.showLoading()
guard let me = provider.authenticationBox.cachedAccount,
let relationship = try? await APIService.shared.relationship(forAccounts: [account], authenticationBox: provider.authenticationBox).value.first else {
return provider.coordinator.hideLoading()
return coordinator.hideLoading()
}
provider.coordinator.hideLoading()
coordinator.hideLoading()
let profileViewModel = ProfileViewModel(
context: provider.context,
context: AppContext.shared,
authenticationBox: provider.authenticationBox,
account: account,
relationship: relationship,
me: me
)
_ = provider.coordinator.present(
_ = coordinator.present(
scene: .profile(viewModel: profileViewModel),
from: provider,
transition: .show
@ -146,13 +155,13 @@ extension DataSourceFacade {
}
let mentions = status.entity.mentions
guard let coordinator = provider.sceneCoordinator else { return }
guard let mention = mentions.first(where: { $0.url == href }) else {
_ = provider.coordinator.present(
_ = coordinator.present(
scene: .safari(url: url),
from: provider,
transition: .safariPresent(animated: true, completion: nil)
)
return
}
@ -163,21 +172,21 @@ extension DataSourceFacade {
extension DataSourceFacade {
static func createActivityViewController(
dependency: NeedsDependency,
dependency: UIViewController,
account: Mastodon.Entity.Account
) -> UIActivityViewController {
let activityViewController = UIActivityViewController(
activityItems: [account.url],
applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)]
applicationActivities: [SafariActivity(sceneCoordinator: dependency.sceneCoordinator)]
)
return activityViewController
}
static func createActivityViewControllerForMastodonUser(status: Status, dependency: NeedsDependency) -> UIActivityViewController {
static func createActivityViewControllerForMastodonUser(status: Status, dependency: UIViewController) -> UIActivityViewController {
let activityViewController = UIActivityViewController(
activityItems: status.activityItems,
applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)]
applicationActivities: [SafariActivity(sceneCoordinator: dependency.sceneCoordinator)]
)
return activityViewController
}

View File

@ -13,7 +13,7 @@ import UIKit
extension DataSourceFacade {
static func responseToCreateSearchHistory(
provider: ViewControllerWithDependencies & AuthContextProvider,
provider: UIViewController & AuthContextProvider,
item: DataSourceItem
) async {
switch item {

View File

@ -8,7 +8,7 @@ import CoreDataStack
extension DataSourceFacade {
public static func getEditHistory(
forStatus status: Status,
provider: NeedsDependency & AuthContextProvider
provider: AuthContextProvider
) async throws -> [Mastodon.Entity.StatusEdit] {
let reponse = try await APIService.shared.getHistory(forStatusID: status.id, authenticationBox: provider.authenticationBox)

View File

@ -20,7 +20,7 @@ import MastodonSDK
extension DataSourceFacade {
static func responseToDeleteStatus(
dependency: NeedsDependency & AuthContextProvider & DataSourceProvider,
dependency: AuthContextProvider & DataSourceProvider,
status: MastodonStatus
) async throws {
let deletedStatus = try await APIService.shared.deleteStatus(
@ -46,7 +46,8 @@ extension DataSourceFacade {
dependency: provider,
status: status
)
_ = provider.coordinator.present(
guard let coordinator = provider.sceneCoordinator else { /* TODO: throw? */ return }
_ = coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: button,
@ -58,7 +59,7 @@ extension DataSourceFacade {
}
private static func createActivityViewController(
dependency: NeedsDependency,
dependency: UIViewController,
status: MastodonStatus
) async throws -> UIActivityViewController {
var activityItems: [Any] = {
@ -68,8 +69,8 @@ extension DataSourceFacade {
]
}()
var applicationActivities: [UIActivity] = [
SafariActivity(sceneCoordinator: dependency.coordinator), // open URL
var applicationActivities: [UIActivity] = await [
SafariActivity(sceneCoordinator: dependency.sceneCoordinator), // open URL
]
if let provider = dependency as? ShareActivityProvider {
@ -95,18 +96,19 @@ extension DataSourceFacade {
sender: UIButton
) async throws {
let _status = status.reblog ?? status
guard let coordinator = provider.sceneCoordinator else { return }
switch action {
case .reply:
FeedbackGenerator.shared.generate(.selectionChanged)
let composeViewModel = ComposeViewModel(
context: provider.context,
authenticationBox: provider.authenticationBox,
composeContext: .composeStatus,
destination: .reply(parent: _status)
)
_ = provider.coordinator.present(
_ = coordinator.present(
scene: .compose(viewModel: composeViewModel),
from: provider,
transition: .modal(animated: true, completion: nil)
@ -144,7 +146,7 @@ extension DataSourceFacade {
@MainActor
static func responseToMenuAction<T>(
dependency: UIViewController & NeedsDependency & AuthContextProvider & DataSourceProvider,
dependency: AuthContextProvider & DataSourceProvider,
action: MastodonMenu.Action,
menuContext: MenuContext,
completion: ((T) -> Void)? = { (param: Void) in }
@ -237,7 +239,7 @@ extension DataSourceFacade {
guard let relationship = try? await APIService.shared.relationship(forAccounts: [menuContext.author], authenticationBox: dependency.authenticationBox).value.first else { return }
let reportViewModel = ReportViewModel(
context: dependency.context,
context: AppContext.shared,
authenticationBox: dependency.authenticationBox,
account: menuContext.author,
relationship: relationship,
@ -245,7 +247,8 @@ extension DataSourceFacade {
contentDisplayMode: .neverConceal
)
_ = dependency.coordinator.present(
guard let coordinator = dependency.sceneCoordinator else { return }
_ = coordinator.present(
scene: .report(viewModel: reportViewModel),
from: dependency,
transition: .modal(animated: true, completion: nil)
@ -256,7 +259,8 @@ extension DataSourceFacade {
account: menuContext.author
)
_ = dependency.coordinator.present(
guard let coordinator = dependency.sceneCoordinator else { return }
_ = coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: menuContext.button,
@ -284,8 +288,8 @@ extension DataSourceFacade {
dependency: dependency,
status: status
)
_ = dependency.coordinator.present(
guard let coordinator = dependency.sceneCoordinator else { return }
_ = coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: menuContext.button,
@ -321,7 +325,7 @@ extension DataSourceFacade {
guard let status = menuContext.statusViewModel?.originalStatus?.reblog ?? menuContext.statusViewModel?.originalStatus else { return }
do {
let translation = try await DataSourceFacade.translateStatus(provider: dependency,status: status)
let translation = try await DataSourceFacade.translateStatus(provider: dependency, status: status)
menuContext.statusViewModel?.translation = translation
} catch TranslationFailure.emptyOrInvalidResponse {
@ -340,11 +344,11 @@ extension DataSourceFacade {
).value
let editStatusViewModel = ComposeViewModel(
context: dependency.coordinator.appContext,
authenticationBox: dependency.authenticationBox,
composeContext: .editStatus(status: status, statusSource: statusSource),
destination: .topLevel)
_ = dependency.coordinator.present(scene: .editStatus(viewModel: editStatusViewModel), transition: .modal(animated: true))
guard let coordinator = dependency.sceneCoordinator else { return }
_ = coordinator.present(scene: .editStatus(viewModel: editStatusViewModel), transition: .modal(animated: true))
case .showOriginal:
// do nothing, as the translation is reverted in `StatusTableViewCellDelegate` in `DataSourceProvider+StatusTableViewCellDelegate.swift`.
@ -416,14 +420,13 @@ extension DataSourceFacade {
assertionFailure()
return
}
dependency.coordinator.present(scene: .safari(url: url), transition: .safariPresent(animated: true))
guard let coordinator = dependency.sceneCoordinator else { return }
coordinator.present(scene: .safari(url: url), transition: .safariPresent(animated: true))
case .copyProfileLink(let url):
UIPasteboard.general.string = url?.absoluteString
case .openUserInBrowser(let url):
guard let url else { return }
dependency.coordinator.present(scene: .safari(url: url), transition: .safariPresent(animated: true))
guard let url, let coordinator = dependency.sceneCoordinator else { return }
coordinator.present(scene: .safari(url: url), transition: .safariPresent(animated: true))
}
}
}
@ -431,7 +434,7 @@ extension DataSourceFacade {
extension DataSourceFacade {
@MainActor
static func responseToToggleSensitiveAction(
dependency: NeedsDependency & DataSourceProvider,
dependency: DataSourceProvider,
status: MastodonStatus
) async throws {
let _status = status.reblog ?? status
@ -443,7 +446,7 @@ extension DataSourceFacade {
}
private extension DataSourceFacade {
static func performDeletion(of status: MastodonStatus, with dependency: NeedsDependency & AuthContextProvider & DataSourceProvider) {
static func performDeletion(of status: MastodonStatus, with dependency: AuthContextProvider & DataSourceProvider) {
Task {
try await DataSourceFacade.responseToDeleteStatus(
dependency: dependency,

View File

@ -13,7 +13,7 @@ import MastodonSDK
extension DataSourceFacade {
static func coordinateToStatusThreadScene(
provider: ViewControllerWithDependencies & AuthContextProvider,
provider: UIViewController,
target: StatusTarget,
status: MastodonStatus
) async {
@ -39,15 +39,16 @@ extension DataSourceFacade {
@MainActor
static func coordinateToStatusThreadScene(
provider: ViewControllerWithDependencies & AuthContextProvider,
provider: UIViewController,
root: StatusItem.Thread
) async {
guard let authBox = AuthenticationServiceProvider.shared.currentActiveUser.value else { return }
let threadViewModel = ThreadViewModel(
context: provider.context,
authenticationBox: provider.authenticationBox,
authenticationBox: authBox,
optionalRoot: root
)
_ = provider.coordinator.present(
guard let coordinator = provider.sceneCoordinator else { return }
_ = coordinator.present(
scene: .thread(viewModel: threadViewModel),
from: provider,
transition: .show

View File

@ -11,15 +11,13 @@ import CoreDataStack
import MastodonCore
import MastodonSDK
typealias Provider = UIViewController & NeedsDependency & AuthContextProvider
extension DataSourceFacade {
enum TranslationFailure: Error {
case emptyOrInvalidResponse
}
public static func translateStatus(
provider: Provider,
provider: AuthContextProvider,
status: MastodonStatus
) async throws -> Mastodon.Entity.Translation {
FeedbackGenerator.shared.generate(.selectionChanged)

View File

@ -16,6 +16,7 @@ extension DataSourceFacade {
provider: DataSourceProvider & AuthContextProvider,
url: URL
) async {
guard let coordinator = await provider.sceneCoordinator else { return }
let domain = provider.authenticationBox.domain
if url.host == domain,
url.pathComponents.count >= 4,
@ -23,10 +24,10 @@ extension DataSourceFacade {
url.pathComponents[1] == "web",
url.pathComponents[2] == "statuses" {
let statusID = url.pathComponents[3]
let threadViewModel = await RemoteThreadViewModel(context: provider.context, authenticationBox: provider.authenticationBox, statusID: statusID)
_ = await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
let threadViewModel = await RemoteThreadViewModel(authenticationBox: provider.authenticationBox, statusID: statusID)
_ = await coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
} else {
_ = await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
_ = await coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
}
}
}

View File

@ -5,10 +5,11 @@ import MastodonUI
import CoreDataStack
import MastodonCore
import MastodonSDK
import UIKit
extension DataSourceFacade {
static func responseToUserViewButtonAction(
dependency: ViewControllerWithDependencies & AuthContextProvider,
dependency: UIViewController & AuthContextProvider,
account: Mastodon.Entity.Account,
buttonState: UserView.ButtonState
) async throws {

View File

@ -184,7 +184,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
],
applicationActivities: []
)
self.coordinator.present(
self.sceneCoordinator?.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: statusCardControl, barButtonItem: nil
@ -200,9 +200,8 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
image: UIImage(systemName: "square.and.pencil")
) {
DispatchQueue.main.async {
self.coordinator.present(
self.sceneCoordinator?.present(
scene: .compose(viewModel: ComposeViewModel(
context: self.context,
authenticationBox: self.authenticationBox,
composeContext: .composeStatus,
destination: .topLevel,
@ -534,12 +533,12 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
assertionFailure("only works for status data provider")
return
}
let userListViewModel = UserListViewModel(
context: context,
let userListViewModel = await UserListViewModel(
context: AppContext.shared,
authenticationBox: authenticationBox,
kind: .rebloggedBy(status: status)
)
_ = await coordinator.present(
_ = await self.sceneCoordinator?.present(
scene: .rebloggedBy(viewModel: userListViewModel),
from: self,
transition: .show
@ -558,12 +557,12 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
assertionFailure("only works for status data provider")
return
}
let userListViewModel = UserListViewModel(
context: context,
let userListViewModel = await UserListViewModel(
context: AppContext.shared,
authenticationBox: authenticationBox,
kind: .favoritedBy(status: status)
)
_ = await coordinator.present(
_ = await self.sceneCoordinator?.present(
scene: .favoritedBy(viewModel: userListViewModel),
from: self,
transition: .show
@ -574,7 +573,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, showEditHistory button: UIButton) {
Task {
await coordinator.showLoading()
await self.sceneCoordinator?.showLoading()
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
guard let item = await self.item(from: source),
@ -586,12 +585,12 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
do {
let edits = try await APIService.shared.getHistory(forStatusID: status.id, authenticationBox: authenticationBox).value
await coordinator.hideLoading()
await self.sceneCoordinator?.hideLoading()
let viewModel = StatusEditHistoryViewModel(status: status, edits: edits, appContext: context, authenticationBox: authenticationBox)
_ = await coordinator.present(scene: .editHistory(viewModel: viewModel), from: self, transition: .show)
let viewModel = await StatusEditHistoryViewModel(status: status, edits: edits, appContext: AppContext.shared, authenticationBox: authenticationBox)
_ = await self.sceneCoordinator?.present(scene: .editHistory(viewModel: viewModel), from: self, transition: .show)
} catch {
await coordinator.hideLoading()
await self.sceneCoordinator?.hideLoading()
}
}
}

View File

@ -88,12 +88,11 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
FeedbackGenerator.shared.generate(.selectionChanged)
let composeViewModel = ComposeViewModel(
context: self.context,
authenticationBox: authenticationBox,
composeContext: .composeStatus,
destination: .reply(parent: status)
)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .compose(viewModel: composeViewModel),
from: self,
transition: .modal(animated: true, completion: nil)

View File

@ -45,12 +45,11 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid
)
} else if let accountWarning = notification.entity.accountWarning {
let url = Mastodon.API.disputesEndpoint(domain: authenticationBox.domain, strikeId: accountWarning.id)
_ = coordinator.present(
self.sceneCoordinator?.present(
scene: .safari(url: url),
from: self,
transition: .safariPresent(animated: true, completion: nil)
)
} else {
await DataSourceFacade.coordinateToProfileScene(
provider: self,
@ -135,7 +134,7 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewableV
title: L10n.Common.Alerts.SavePhotoFailure.title,
message: L10n.Common.Alerts.SavePhotoFailure.message
)
_ = self.coordinator.present(
self.sceneCoordinator?.present(
scene: .alertController(alertController: alertController),
from: self,
transition: .alertController(animated: true, completion: nil)
@ -166,10 +165,10 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewableV
attributes: [],
state: .off
) { [weak self] _ in
guard let self = self else { return }
guard let self = self, let coordinator = self.sceneCoordinator else { return }
Task {
let applicationActivities: [UIActivity] = [
SafariActivity(sceneCoordinator: self.coordinator)
SafariActivity(sceneCoordinator: coordinator)
]
let activityViewController = UIActivityViewController(
activityItems: [assetURL],

View File

@ -36,7 +36,7 @@ extension DataSourceItem {
}
}
protocol DataSourceProvider: ViewControllerWithDependencies {
protocol DataSourceProvider: UIViewController {
func item(from source: DataSourceItem.Source) async -> DataSourceItem?
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent)

View File

@ -20,7 +20,6 @@ final class AccountListViewModel: NSObject {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
// output
@ -28,8 +27,7 @@ final class AccountListViewModel: NSObject {
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>!
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
super.init()

View File

@ -12,10 +12,7 @@ import MastodonAsset
import MastodonLocalization
import MastodonCore
final class AccountListViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class AccountListViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: AccountListViewModel!
@ -83,7 +80,7 @@ extension AccountListViewController {
extension AccountListViewController {
@objc private func addBarButtonItem(_ sender: UIBarButtonItem) {
_ = coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
}
override func accessibilityPerformEscape() -> Bool {
@ -115,7 +112,7 @@ extension AccountListViewController: UITableViewDelegate {
FileManager.default.invalidateHomeTimelineCache(for: userIdentifier)
FileManager.default.invalidateNotificationsAll(for: userIdentifier)
FileManager.default.invalidateNotificationsMentions(for: userIdentifier)
self.coordinator.setup()
self.sceneCoordinator?.setup()
} catch {
assertionFailure("Failed to delete Authentication: \(error)")
@ -146,23 +143,23 @@ extension AccountListViewController: UITableViewDelegate {
Task { @MainActor in
let isActive = AuthenticationServiceProvider.shared.activateExistingUser(record.userID, inDomain: record.domain)
guard isActive else { return }
self.coordinator.setup()
self.sceneCoordinator?.setup()
} // end Task
case .addAccount:
// TODO: add dismiss entry for welcome scene
_ = coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
case .logoutOfAllAccounts:
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let logoutAction = UIAlertAction(title: L10n.Scene.AccountList.logoutAllAccounts, style: .destructive) { _ in
Task { @MainActor in
self.coordinator.showLoading()
self.sceneCoordinator?.showLoading()
for authenticationBox in AuthenticationServiceProvider.shared.mastodonAuthenticationBoxes {
try? await AuthenticationServiceProvider.shared.signOutMastodonUser(authentication: authenticationBox.authentication)
}
self.coordinator.hideLoading()
self.sceneCoordinator?.hideLoading()
self.coordinator.setup()
self.sceneCoordinator?.setup()
}
}

View File

@ -17,11 +17,8 @@ import MastodonUI
import MastodonLocalization
import MastodonSDK
final class ComposeViewController: UIViewController, NeedsDependency {
final class ComposeViewController: UIViewController {
static let minAutoCompleteVisibleHeight: CGFloat = 100
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
var viewModel: ComposeViewModel
@ -48,7 +45,6 @@ final class ComposeViewController: UIViewController, NeedsDependency {
}
return ComposeContentViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
composeContext: composeContext,
destination: viewModel.destination,
@ -217,7 +213,7 @@ extension ComposeViewController {
let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
_ = coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
return
}
@ -235,7 +231,7 @@ extension ComposeViewController {
self?.enqueuePublishStatus()
}
alertController.addAction(confirmAction)
_ = coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
return
}
@ -266,7 +262,7 @@ extension ComposeViewController {
let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
_ = coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
return
}
@ -284,7 +280,7 @@ extension ComposeViewController {
self?.enqueuePublishStatusEdit()
}
alertController.addAction(confirmAction)
_ = coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
return
}

View File

@ -29,7 +29,6 @@ final class ComposeViewModel {
let id = UUID()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let composeContext: Context
let destination: ComposeContentViewModel.Destination
@ -43,13 +42,11 @@ final class ComposeViewModel {
@Published var title: String
init(
context: AppContext,
authenticationBox: MastodonAuthenticationBox,
composeContext: ComposeViewModel.Context,
destination: ComposeContentViewModel.Destination,
initialContent: String = ""
) {
self.context = context
self.authenticationBox = authenticationBox
self.destination = destination
self.initialContent = initialContent

View File

@ -37,7 +37,6 @@ extension DiscoverySection {
static func diffableDataSource(
tableView: UITableView,
context: AppContext,
configuration: Configuration
) -> UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem> {

View File

@ -12,15 +12,12 @@ import MastodonAsset
import MastodonCore
import MastodonUI
public class DiscoveryViewController: PageboyViewController, NeedsDependency {
public class DiscoveryViewController: PageboyViewController {
public static let containerViewMarginForRegularHorizontalSizeClass: CGFloat = 64
public static let containerViewMarginForCompactHorizontalSizeClass: CGFloat = 16
var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: DiscoveryViewModel!

View File

@ -16,7 +16,6 @@ final class DiscoveryViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let discoveryPostsViewController: DiscoveryPostsViewController
let discoveryHashtagsViewController: DiscoveryHashtagsViewController
@ -26,37 +25,27 @@ final class DiscoveryViewModel {
@Published var viewControllers: [ScrollViewContainer]
@MainActor
init(context: AppContext, coordinator: SceneCoordinator, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
func setupDependency(_ needsDependency: NeedsDependency) {
needsDependency.context = context
needsDependency.coordinator = coordinator
}
discoveryPostsViewController = {
let viewController = DiscoveryPostsViewController()
setupDependency(viewController)
viewController.viewModel = DiscoveryPostsViewModel(context: context, authenticationBox: authenticationBox)
viewController.viewModel = DiscoveryPostsViewModel(authenticationBox: authenticationBox)
return viewController
}()
discoveryHashtagsViewController = {
let viewController = DiscoveryHashtagsViewController()
setupDependency(viewController)
viewController.viewModel = DiscoveryHashtagsViewModel(context: context, authenticationBox: authenticationBox)
viewController.viewModel = DiscoveryHashtagsViewModel(authenticationBox: authenticationBox)
return viewController
}()
discoveryNewsViewController = {
let viewController = DiscoveryNewsViewController()
setupDependency(viewController)
viewController.viewModel = DiscoveryNewsViewModel(context: context, authenticationBox: authenticationBox)
viewController.viewModel = DiscoveryNewsViewModel(authenticationBox: authenticationBox)
return viewController
}()
discoveryForYouViewController = {
let viewController = DiscoveryForYouViewController()
setupDependency(viewController)
viewController.viewModel = DiscoveryForYouViewModel(context: context, authenticationBox: authenticationBox)
viewController.viewModel = DiscoveryForYouViewModel(authenticationBox: authenticationBox)
return viewController
}()
self.viewControllers = [

View File

@ -11,10 +11,7 @@ import MastodonUI
import MastodonCore
import MastodonSDK
final class DiscoveryForYouViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class DiscoveryForYouViewController: UIViewController, MediaPreviewableViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: DiscoveryForYouViewModel!
@ -137,7 +134,7 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate {
guard let indexPath = tableView.indexPath(for: cell) else { return }
guard case let .account(account, _) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
coordinator.showLoading()
self.sceneCoordinator?.showLoading()
Task { [weak self] in
@ -147,16 +144,15 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate {
let familiarFollowers = viewModel.familiarFollowers.first(where: { $0.id == userID })?.accounts ?? []
let relationships = try await APIService.shared.relationship(forAccounts: familiarFollowers, authenticationBox: authenticationBox).value
coordinator.hideLoading()
self.sceneCoordinator?.hideLoading()
let familiarFollowersViewModel = FamiliarFollowersViewModel(
context: context,
authenticationBox: authenticationBox,
accounts: familiarFollowers,
relationships: relationships
)
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .familiarFollowers(viewModel: familiarFollowersViewModel),
from: self,
transition: .show

View File

@ -17,7 +17,6 @@ extension DiscoveryForYouViewModel {
) {
diffableDataSource = DiscoverySection.diffableDataSource(
tableView: tableView,
context: context,
configuration: DiscoverySection.Configuration(
authenticationBox: authenticationBox,
profileCardTableViewCellDelegate: profileCardTableViewCellDelegate,

View File

@ -16,7 +16,6 @@ final class DiscoveryForYouViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
@MainActor
@ -29,8 +28,7 @@ final class DiscoveryForYouViewModel {
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
let didLoadLatest = PassthroughSubject<Void, Never>()
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
self.accounts = []
self.relationships = []

View File

@ -10,10 +10,7 @@ import Combine
import MastodonCore
import MastodonUI
final class DiscoveryHashtagsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class DiscoveryHashtagsViewController: UIViewController, MediaPreviewableViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: DiscoveryHashtagsViewModel!
@ -88,8 +85,8 @@ extension DiscoveryHashtagsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard case let .hashtag(tag) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authenticationBox: viewModel.authenticationBox, hashtag: tag.name)
_ = coordinator.present(
let hashtagTimelineViewModel = HashtagTimelineViewModel(authenticationBox: viewModel.authenticationBox, hashtag: tag.name)
_ = self.sceneCoordinator?.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: self,
transition: .show
@ -198,8 +195,8 @@ extension DiscoveryHashtagsViewController: TableViewControllerNavigateable {
guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return }
guard case let .hashtag(tag) = item else { return }
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authenticationBox: viewModel.authenticationBox, hashtag: tag.name)
_ = coordinator.present(
let hashtagTimelineViewModel = HashtagTimelineViewModel(authenticationBox: viewModel.authenticationBox, hashtag: tag.name)
_ = self.sceneCoordinator?.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: self,
transition: .show

View File

@ -14,7 +14,6 @@ extension DiscoveryHashtagsViewModel {
) {
diffableDataSource = DiscoverySection.diffableDataSource(
tableView: tableView,
context: context,
configuration: DiscoverySection.Configuration(authenticationBox: authenticationBox)
)

View File

@ -18,7 +18,6 @@ final class DiscoveryHashtagsViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let viewDidAppeared = PassthroughSubject<Void, Never>()
@ -26,8 +25,7 @@ final class DiscoveryHashtagsViewModel {
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
@Published var hashtags: [Mastodon.Entity.Tag] = []
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
// end init

View File

@ -10,10 +10,7 @@ import Combine
import MastodonCore
import MastodonUI
final class DiscoveryNewsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class DiscoveryNewsViewController: UIViewController, MediaPreviewableViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: DiscoveryNewsViewModel!
@ -85,7 +82,7 @@ extension DiscoveryNewsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard case let .link(link) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
guard let url = URL(string: link.url) else { return }
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .safari(url: url),
from: self,
transition: .safariPresent(animated: true, completion: nil)
@ -182,7 +179,7 @@ extension DiscoveryNewsViewController: TableViewControllerNavigateable {
guard case let .link(link) = item else { return }
guard let url = URL(string: link.url) else { return }
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .safari(url: url),
from: self,
transition: .safariPresent(animated: true, completion: nil)

View File

@ -15,7 +15,6 @@ extension DiscoveryNewsViewModel {
) {
diffableDataSource = DiscoverySection.diffableDataSource(
tableView: tableView,
context: context,
configuration: DiscoverySection.Configuration(authenticationBox: authenticationBox)
)

View File

@ -18,7 +18,6 @@ final class DiscoveryNewsViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
// output
@ -40,8 +39,7 @@ final class DiscoveryNewsViewModel {
let didLoadLatest = PassthroughSubject<Void, Never>()
@Published var isServerSupportEndpoint = true
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
// end init

View File

@ -10,9 +10,7 @@ import Combine
import MastodonCore
import MastodonUI
final class DiscoveryPostsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class DiscoveryPostsViewController: UIViewController, MediaPreviewableViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: DiscoveryPostsViewModel!

View File

@ -16,9 +16,7 @@ extension DiscoveryPostsViewModel {
) {
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
context: context,
authenticationBox: authenticationBox,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,

View File

@ -18,7 +18,6 @@ final class DiscoveryPostsViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let dataController: StatusDataController
@ -41,8 +40,7 @@ final class DiscoveryPostsViewModel {
@Published var isServerSupportEndpoint = true
@MainActor
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
self.dataController = StatusDataController()

View File

@ -10,17 +10,15 @@ import UIKit
class NewDonationNavigationFlow: NavigationFlow {
private let campaign: DonationCampaignViewModel
private let appContext: AppContext
private let authenticationBox: MastodonAuthenticationBox
private let sceneCoordinator: SceneCoordinator
init(
flowPresenter: NavigationFlowPresenter,
campaign: DonationCampaignViewModel, appContext: AppContext,
campaign: DonationCampaignViewModel,
authenticationBox: MastodonAuthenticationBox, sceneCoordinator: SceneCoordinator
) {
self.campaign = campaign
self.appContext = appContext
self.authenticationBox = authenticationBox
self.sceneCoordinator = sceneCoordinator
super.init(flowPresenter: flowPresenter)
@ -106,7 +104,6 @@ class NewDonationNavigationFlow: NavigationFlow {
private func composeDonationSuccessPost(_ suggestedText: String) {
let composeViewModel = ComposeViewModel(
context: appContext,
authenticationBox: authenticationBox,
composeContext: .composeStatus,
destination: .topLevel,

View File

@ -16,10 +16,7 @@ import MastodonUI
import MastodonLocalization
import MastodonSDK
final class HashtagTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class HashtagTimelineViewController: UIViewController, MediaPreviewableViewController {
let mediaPreviewTransitionController = MediaPreviewTransitionController()
@ -184,13 +181,12 @@ extension HashtagTimelineViewController {
let hashtag = "#" + viewModel.hashtag
UITextChecker.learnWord(hashtag)
let composeViewModel = ComposeViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
composeContext: .composeStatus,
destination: .topLevel,
initialContent: hashtag
)
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
}

View File

@ -17,9 +17,7 @@ extension HashtagTimelineViewModel {
) {
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
context: context,
authenticationBox: authenticationBox,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,

View File

@ -22,7 +22,6 @@ final class HashtagTimelineViewModel {
var needLoadMiddleIndex: Int? = nil
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let dataController: StatusDataController
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
@ -50,8 +49,7 @@ final class HashtagTimelineViewModel {
}()
@MainActor
init(context: AppContext, authenticationBox: MastodonAuthenticationBox, hashtag: String) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox, hashtag: String) {
self.authenticationBox = authenticationBox
self.hashtag = hashtag
self.dataController = StatusDataController()

View File

@ -19,10 +19,7 @@ import MastodonCore
import MastodonUI
import MastodonLocalization
final class HomeTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class HomeTimelineViewController: UIViewController, MediaPreviewableViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: HomeTimelineViewModel?
@ -616,9 +613,9 @@ extension HomeTimelineViewController {
@objc private func findPeopleButtonPressed(_ sender: Any?) {
guard let authenticationBox = viewModel?.authenticationBox else { return }
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context, authenticationBox: authenticationBox)
let suggestionAccountViewModel = SuggestionAccountViewModel(authenticationBox: authenticationBox)
suggestionAccountViewModel.delegate = viewModel
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
from: self,
transition: .modal(animated: true, completion: nil)
@ -626,13 +623,13 @@ extension HomeTimelineViewController {
}
@objc private func manuallySearchButtonPressed(_ sender: UIButton) {
coordinator.switchToTabBar(tab: .search)
self.sceneCoordinator?.switchToTabBar(tab: .search)
}
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
guard let setting = SettingService.shared.currentSetting.value else { return }
_ = coordinator.present(scene: .settings(setting: setting), from: self, transition: .none)
_ = self.sceneCoordinator?.present(scene: .settings(setting: setting), from: self, transition: .none)
}
@objc private func refreshControlValueChanged(_ sender: RefreshControl) {
@ -650,7 +647,7 @@ extension HomeTimelineViewController {
FileManager.default.invalidateHomeTimelineCache(for: userIdentifier)
FileManager.default.invalidateNotificationsAll(for: userIdentifier)
FileManager.default.invalidateNotificationsMentions(for: userIdentifier)
self.coordinator.setup()
self.sceneCoordinator?.setup()
}
}
@ -741,7 +738,8 @@ extension HomeTimelineViewController {
private func showDonationCampaign(_ campaign: Mastodon.Entity.DonationCampaign) {
hideDonationCampaignBanner()
navigationFlow = NewDonationNavigationFlow(flowPresenter: self, campaign: campaign, appContext: context, authenticationBox: authenticationBox, sceneCoordinator: coordinator)
guard let coordinator = self.sceneCoordinator else { return }
navigationFlow = NewDonationNavigationFlow(flowPresenter: self, campaign: campaign, authenticationBox: authenticationBox, sceneCoordinator: coordinator)
navigationFlow?.presentFlow { [weak self] in
self?.navigationFlow = nil
}

View File

@ -20,9 +20,7 @@ extension HomeTimelineViewModel {
) {
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
context: context,
authenticationBox: authenticationBox,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,

View File

@ -23,7 +23,6 @@ final class HomeTimelineViewModel: NSObject {
var observations = Set<NSKeyValueObservation>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let dataController: FeedDataController
@ -91,10 +90,9 @@ final class HomeTimelineViewModel: NSObject {
var cellFrameCache = NSCache<NSNumber, NSValue>()
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
self.dataController = FeedDataController(context: context, authenticationBox: authenticationBox, kind: .home(timeline: timelineContext))
self.dataController = FeedDataController(authenticationBox: authenticationBox, kind: .home(timeline: timelineContext))
super.init()
self.dataController.records = (try? PersistenceManager.shared.cachedTimeline(.homeTimeline(authenticationBox)).map {
MastodonFeed.fromStatus($0, kind: .home)

View File

@ -13,10 +13,7 @@ import MastodonCore
import MastodonUI
import MastodonLocalization
final class MediaPreviewViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class MediaPreviewViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: MediaPreviewViewModel!
@ -285,7 +282,7 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
title: L10n.Common.Alerts.SavePhotoFailure.title,
message: L10n.Common.Alerts.SavePhotoFailure.message
)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .alertController(alertController: alertController),
from: self,
transition: .alertController(animated: true, completion: nil)
@ -296,7 +293,7 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
} receiveValue: { _ in
// do nothing
}
.store(in: &context.disposeBag)
.store(in: &AppContext.shared.disposeBag)
case .copyPhoto:
guard let assetURL = viewController.viewModel.item.assetURL else { return }
@ -311,10 +308,10 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
} receiveValue: { _ in
// do nothing
}
.store(in: &context.disposeBag)
.store(in: &AppContext.shared.disposeBag)
case .share:
let applicationActivities: [UIActivity] = [
SafariActivity(sceneCoordinator: self.coordinator)
SafariActivity(sceneCoordinator: self.sceneCoordinator)
]
let activityViewController = UIActivityViewController(
activityItems: {

View File

@ -15,10 +15,10 @@ class AccountNotificationTimelineViewController: NotificationTimelineViewControl
let request: Mastodon.Entity.NotificationRequest
weak var delegate: AccountNotificationTimelineViewControllerDelegate?
init(viewModel: NotificationTimelineViewModel, context: AppContext, coordinator: SceneCoordinator, notificationRequest: Mastodon.Entity.NotificationRequest) {
init(viewModel: NotificationTimelineViewModel, notificationRequest: Mastodon.Entity.NotificationRequest) {
self.request = notificationRequest
super.init(viewModel: viewModel, context: context, coordinator: coordinator)
super.init(viewModel: viewModel)
navigationItem.rightBarButtonItem = UIBarButtonItem(title: nil, image: UIImage(systemName: "ellipsis.circle"), target: nil, action: nil, menu: menu())
}
@ -32,18 +32,18 @@ class AccountNotificationTimelineViewController: NotificationTimelineViewControl
UIAction(title: L10n.Scene.Notification.FilteredNotification.accept, image: UIImage(systemName: "checkmark")) { [weak self] _ in
guard let self else { return }
coordinator.showLoading()
self.sceneCoordinator?.showLoading()
self.navigationController?.popViewController(animated: true)
self.delegate?.acceptRequest(self, request: request)
coordinator.hideLoading()
self.sceneCoordinator?.hideLoading()
},
UIAction(title: L10n.Scene.Notification.FilteredNotification.dismiss, image: NotificationRequestConstants.dismissIcon) { [weak self] _ in
guard let self else { return }
coordinator.showLoading()
self.sceneCoordinator?.showLoading()
self.navigationController?.popViewController(animated: true)
self.delegate?.dismissRequest(self, request: request)
coordinator.hideLoading()
self.sceneCoordinator?.hideLoading()
}
])

View File

@ -18,9 +18,8 @@ protocol NotificationRequestsTableViewControllerDelegate: AnyObject {
func notificationRequestsUpdated(_ viewController: NotificationRequestsTableViewController)
}
class NotificationRequestsTableViewController: UIViewController, NeedsDependency {
var context: AppContext!
var coordinator: SceneCoordinator!
class NotificationRequestsTableViewController: UIViewController {
weak var delegate: NotificationRequestsTableViewControllerDelegate?
let tableView: UITableView
@ -30,8 +29,6 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency
init(viewModel: NotificationRequestsViewModel) {
self.viewModel = viewModel
self.context = viewModel.appContext
self.coordinator = viewModel.coordinator
tableView = UITableView(frame: .zero)
tableView.translatesAutoresizingMaskIntoConstraints = false

View File

@ -5,16 +5,12 @@ import MastodonSDK
import MastodonCore
struct NotificationRequestsViewModel {
let appContext: AppContext
let authenticationBox: MastodonAuthenticationBox
let coordinator: SceneCoordinator
var requests: [Mastodon.Entity.NotificationRequest]
init(appContext: AppContext, authenticationBox: MastodonAuthenticationBox, coordinator: SceneCoordinator, requests: [Mastodon.Entity.NotificationRequest]) {
self.appContext = appContext
init(authenticationBox: MastodonAuthenticationBox, requests: [Mastodon.Entity.NotificationRequest]) {
self.authenticationBox = authenticationBox
self.coordinator = coordinator
self.requests = requests
}
}

View File

@ -32,7 +32,6 @@ extension NotificationSection {
static func diffableDataSource(
tableView: UITableView,
context: AppContext,
configuration: Configuration
) -> UITableViewDiffableDataSource<NotificationSection, NotificationItem> {
tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self))
@ -50,7 +49,6 @@ extension NotificationSection {
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
configure(
context: context,
tableView: tableView,
cell: cell,
viewModel: NotificationTableViewCell.ViewModel(value: .feed(feed)),
@ -81,20 +79,17 @@ extension NotificationSection {
extension NotificationSection {
static func configure(
context: AppContext,
tableView: UITableView,
cell: NotificationTableViewCell,
viewModel: NotificationTableViewCell.ViewModel,
configuration: Configuration
) {
StatusSection.setupStatusPollDataSource(
context: context,
authenticationBox: configuration.authenticationBox,
statusView: cell.notificationView.statusView
)
StatusSection.setupStatusPollDataSource(
context: context,
authenticationBox: configuration.authenticationBox,
statusView: cell.notificationView.quoteStatusView
)

View File

@ -11,10 +11,7 @@ import CoreDataStack
import MastodonCore
import MastodonLocalization
class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext!
weak var coordinator: SceneCoordinator!
class NotificationTimelineViewController: UIViewController, MediaPreviewableViewController {
let mediaPreviewTransitionController = MediaPreviewTransitionController()
@ -39,10 +36,8 @@ class NotificationTimelineViewController: UIViewController, NeedsDependency, Med
let cellFrameCache = NSCache<NSNumber, NSValue>()
init(viewModel: NotificationTimelineViewModel, context: AppContext, coordinator: SceneCoordinator) {
init(viewModel: NotificationTimelineViewModel) {
self.viewModel = viewModel
self.context = context
self.coordinator = coordinator
super.init(nibName: nil, bundle: nil)
@ -288,11 +283,10 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
if let status = notification.status {
let threadViewModel = ThreadViewModel(
context: self.context,
authenticationBox: self.viewModel.authenticationBox,
optionalRoot: .root(context: .init(status: .fromEntity(status)))
)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .thread(viewModel: threadViewModel),
from: self,
transition: .show

View File

@ -18,7 +18,6 @@ extension NotificationTimelineViewModel {
) {
diffableDataSource = NotificationSection.diffableDataSource(
tableView: tableView,
context: context,
configuration: NotificationSection.Configuration(
authenticationBox: authenticationBox,
notificationTableViewCellDelegate: notificationTableViewCellDelegate,

View File

@ -19,7 +19,6 @@ final class NotificationTimelineViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let scope: Scope
var notificationPolicy: Mastodon.Entity.NotificationPolicy?
@ -47,15 +46,13 @@ final class NotificationTimelineViewModel {
@MainActor
init(
context: AppContext,
authenticationBox: MastodonAuthenticationBox,
scope: Scope,
notificationPolicy: Mastodon.Entity.NotificationPolicy? = nil
) {
self.context = context
self.authenticationBox = authenticationBox
self.scope = scope
self.dataController = FeedDataController(context: context, authenticationBox: authenticationBox, kind: scope.feedKind)
self.dataController = FeedDataController(authenticationBox: authenticationBox, kind: scope.feedKind)
self.notificationPolicy = notificationPolicy
switch scope {

View File

@ -14,10 +14,7 @@ import Pageboy
import MastodonCore
import MastodonSDK
final class NotificationViewController: TabmanViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class NotificationViewController: TabmanViewController {
var disposeBag = Set<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
@ -129,7 +126,7 @@ extension NotificationViewController {
privateMentions: policy.filterPrivateMentions
)
guard let policyViewController = coordinator.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) as? NotificationPolicyViewController else { return }
guard let policyViewController = self.sceneCoordinator?.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) as? NotificationPolicyViewController else { return }
policyViewController.delegate = self
}
@ -156,12 +153,9 @@ extension NotificationViewController {
let viewController = NotificationTimelineViewController(
viewModel: NotificationTimelineViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
scope: scope, notificationPolicy: viewModel.notificationPolicy
),
context: context,
coordinator: coordinator
)
)
return viewController

View File

@ -13,13 +13,10 @@ import MastodonCore
import MastodonUI
import MastodonLocalization
final class MastodonConfirmEmailViewController: UIViewController, NeedsDependency {
final class MastodonConfirmEmailViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: MastodonConfirmEmailViewModel!
let stackView = UIStackView()
@ -126,10 +123,10 @@ extension MastodonConfirmEmailViewController {
} receiveValue: { _ in
// do nothing
}
.store(in: &self.context.disposeBag) // execute in the background
.store(in: &AppContext.shared.disposeBag) // execute in the background
} // end switch
} receiveValue: { _ in
self.coordinator.setup()
self.sceneCoordinator?.setup()
// self.dismiss(animated: true, completion: nil)
}
.store(in: &self.disposeBag)
@ -208,13 +205,13 @@ extension MastodonConfirmEmailViewController {
let resendAction = UIAlertAction(title: L10n.Scene.ConfirmEmail.DontReceiveEmail.resendEmail, style: .default) { _ in
let url = Mastodon.API.resendEmailURL(domain: self.viewModel.authenticateInfo.domain)
let viewModel = MastodonResendEmailViewModel(resendEmailURL: url, email: self.viewModel.email)
_ = self.coordinator.present(scene: .mastodonResendEmail(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .mastodonResendEmail(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
}
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) { _ in
}
alertController.addAction(resendAction)
alertController.addAction(okAction)
_ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
}

View File

@ -17,7 +17,7 @@ enum CategoryPickerSection: Equatable, Hashable {
extension CategoryPickerSection {
static func collectionViewDiffableDataSource(
for collectionView: UICollectionView,
dependency: NeedsDependency,
dependency: UIViewController,
viewModel: MastodonPickServerViewModel
) -> UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem> {
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in

View File

@ -14,11 +14,7 @@ import MastodonLocalization
import MastodonUI
import MastodonSDK
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
var context: AppContext! {
get { return AppContext.shared }
set { }
}
final class MastodonPickServerViewController: UIViewController {
var coordinator: SceneCoordinator!

View File

@ -13,7 +13,7 @@ extension MastodonPickServerViewModel {
func setupDiffableDataSource(
for tableView: UITableView,
dependency: NeedsDependency,
dependency: UIViewController,
pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate
) {
// set section header

View File

@ -18,7 +18,7 @@ enum PickServerSection: Equatable, Hashable {
extension PickServerSection {
static func tableViewDiffableDataSource(
for tableView: UITableView,
dependency: NeedsDependency
dependency: UIViewController
) -> UITableViewDiffableDataSource<PickServerSection, PickServerItem> {
tableView.register(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self))
tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self))

View File

@ -16,15 +16,12 @@ import MastodonAsset
import MastodonCore
import MastodonLocalization
final class MastodonRegisterViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance {
final class MastodonRegisterViewController: UIViewController, OnboardingViewControllerAppearance {
static let avatarImageMaxSizeInPixel = CGSize(width: 400, height: 400)
var disposeBag = Set<AnyCancellable>()
private var observations = Set<NSKeyValueObservation>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: MastodonRegisterViewModel!
private(set) lazy var mastodonRegisterView = MastodonRegisterView(viewModel: viewModel)
@ -92,7 +89,7 @@ extension MastodonRegisterViewController {
let alertController = UIAlertController(for: error, title: L10n.Common.Alerts.SignUpFailure.title, preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)

View File

@ -10,10 +10,7 @@ import UIKit
import WebKit
import MastodonCore
final class MastodonResendEmailViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class MastodonResendEmailViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: MastodonResendEmailViewModel!

View File

@ -12,7 +12,7 @@ import MastodonCore
import MastodonLocalization
import MastodonSDK
final class WelcomeViewController: UIViewController, NeedsDependency {
final class WelcomeViewController: UIViewController {
private enum Constants {
static let topAnchorInset: CGFloat = 20
@ -28,16 +28,13 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
}
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
private let authenticationViewModel = AuthenticationViewModel()
private var authenticationStateTask: Task<(), Never>?
var disposeBag = Set<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
private(set) lazy var viewModel = WelcomeViewModel(context: context)
private(set) lazy var viewModel = WelcomeViewModel()
let welcomeIllustrationView = WelcomeIllustrationView()
let separatorView = WelcomeSeparatorView(frame: .zero)
@ -137,7 +134,7 @@ extension WelcomeViewController {
let alertController = UIAlertController(for: error, title: "Error", preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
@ -151,25 +148,25 @@ extension WelcomeViewController {
case .error(let error):
displayError(error)
case .logInToExistingAccountRequested:
_ = coordinator.present(scene: .mastodonLogin(authenticationViewModel: authenticationViewModel, suggestedDomain: viewModel.randomDefaultServer?.domain), from: self, transition: .show)
_ = self.sceneCoordinator?.present(scene: .mastodonLogin(authenticationViewModel: authenticationViewModel, suggestedDomain: viewModel.randomDefaultServer?.domain), from: self, transition: .show)
case .joiningServer:
break
case .showingRules(let viewModel):
if let viewModel {
_ = coordinator.present(scene: .mastodonServerRules(viewModel: viewModel), from: self, transition: .show)
_ = self.sceneCoordinator?.present(scene: .mastodonServerRules(viewModel: viewModel), from: self, transition: .show)
} else {
popBack()
}
case .registering(let viewModel):
_ = coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show)
_ = self.sceneCoordinator?.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show)
case .showingPrivacyPolicy(let viewModel):
_ = coordinator.present(scene: .mastodonPrivacyPolicies(viewModel: viewModel), from: self, transition: .show)
_ = self.sceneCoordinator?.present(scene: .mastodonPrivacyPolicies(viewModel: viewModel), from: self, transition: .show)
case .pickingServer:
_ = coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(joinServer: { [weak self] server in try await self?.authenticationViewModel.joinServer(server) }, displayError: { [weak self] error in self?.displayError(error) })), from: self, transition: .show)
_ = self.sceneCoordinator?.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(joinServer: { [weak self] server in try await self?.authenticationViewModel.joinServer(server) }, displayError: { [weak self] error in self?.displayError(error) })), from: self, transition: .show)
case .confirmingEmail(let viewModel):
_ = coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
_ = self.sceneCoordinator?.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
case .authenticatedUser(let authBox):
coordinator.setup()
self.sceneCoordinator?.setup()
break
case .authenticatingUser:
break

View File

@ -16,16 +16,11 @@ final class WelcomeViewModel {
var disposeBag = Set<AnyCancellable>()
private(set) var defaultServers: [Mastodon.Entity.DefaultServer]?
var randomDefaultServer: Mastodon.Entity.Server?
// input
let context: AppContext
// output
@Published var needsShowDismissEntry = false
init(context: AppContext) {
self.context = context
init() {
AuthenticationServiceProvider.shared.$mastodonAuthenticationBoxes
.map { !$0.isEmpty }
.assign(to: &$needsShowDismissEntry)

View File

@ -19,9 +19,6 @@ protocol ProfileAboutViewControllerDelegate: AnyObject {
final class ProfileAboutViewController: UIViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
weak var delegate: ProfileAboutViewControllerDelegate?
var disposeBag = Set<AnyCancellable>()

View File

@ -14,10 +14,7 @@ import MastodonCore
import MastodonUI
import MastodonLocalization
final class BookmarkViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class BookmarkViewController: UIViewController, MediaPreviewableViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: BookmarkViewModel!

View File

@ -15,9 +15,7 @@ extension BookmarkViewModel {
) {
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
context: context,
authenticationBox: authenticationBox,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,

View File

@ -17,7 +17,6 @@ final class BookmarkViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let dataController: StatusDataController
@ -38,8 +37,7 @@ final class BookmarkViewModel {
}()
@MainActor
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
self.dataController = StatusDataController()
}

View File

@ -12,19 +12,14 @@ import MastodonLocalization
import MastodonUI
import MastodonSDK
final class FamiliarFollowersViewController: UIViewController, NeedsDependency {
final class FamiliarFollowersViewController: UIViewController {
weak var context: AppContext!
weak var coordinator: SceneCoordinator!
let viewModel: FamiliarFollowersViewModel
let tableView: UITableView
init(viewModel: FamiliarFollowersViewModel, context: AppContext, coordinator: SceneCoordinator) {
init(viewModel: FamiliarFollowersViewModel) {
self.viewModel = viewModel
self.context = context
self.coordinator = coordinator
tableView = UITableView()
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none

View File

@ -10,7 +10,6 @@ import MastodonCore
import MastodonSDK
final class FamiliarFollowersViewModel {
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
var accounts: [Mastodon.Entity.Account]
@ -19,8 +18,7 @@ final class FamiliarFollowersViewModel {
// output
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
init(context: AppContext, authenticationBox: MastodonAuthenticationBox, accounts: [Mastodon.Entity.Account], relationships: [Mastodon.Entity.Relationship]) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox, accounts: [Mastodon.Entity.Account], relationships: [Mastodon.Entity.Relationship]) {
self.authenticationBox = authenticationBox
self.accounts = accounts
self.relationships = relationships
@ -32,7 +30,6 @@ final class FamiliarFollowersViewModel {
) {
diffableDataSource = UserSection.diffableDataSource(
tableView: tableView,
context: context,
authenticationBox: authenticationBox,
userTableViewCellDelegate: userTableViewCellDelegate
)

View File

@ -17,10 +17,7 @@ import MastodonCore
import MastodonUI
import MastodonLocalization
final class FavoriteViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class FavoriteViewController: UIViewController, MediaPreviewableViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: FavoriteViewModel!

View File

@ -15,9 +15,7 @@ extension FavoriteViewModel {
) {
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
context: context,
authenticationBox: authenticationBox,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,

View File

@ -17,7 +17,6 @@ final class FavoriteViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let dataController: StatusDataController
@ -37,8 +36,7 @@ final class FavoriteViewModel {
}()
@MainActor
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
self.dataController = StatusDataController()
}

View File

@ -11,9 +11,7 @@ import MastodonCore
import MastodonUI
import MastodonLocalization
final class FollowedTagsViewController: UIViewController, NeedsDependency {
var context: AppContext!
var coordinator: SceneCoordinator!
final class FollowedTagsViewController: UIViewController {
let authenticationBox: MastodonAuthenticationBox
var viewModel: FollowedTagsViewModel
@ -22,9 +20,7 @@ final class FollowedTagsViewController: UIViewController, NeedsDependency {
let tableView: UITableView
let refreshControl: UIRefreshControl
init(appContext: AppContext, sceneCoordinator: SceneCoordinator, authenticationBox: MastodonAuthenticationBox, viewModel: FollowedTagsViewModel) {
self.context = appContext
self.coordinator = sceneCoordinator
init(authenticationBox: MastodonAuthenticationBox, viewModel: FollowedTagsViewModel) {
self.authenticationBox = authenticationBox
self.viewModel = viewModel
@ -76,12 +72,11 @@ extension FollowedTagsViewController: UITableViewDelegate {
let object = viewModel.followedTags[indexPath.row]
let hashtagTimelineViewModel = HashtagTimelineViewModel(
context: self.context,
authenticationBox: self.authenticationBox,
hashtag: object.name
)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: self,
transition: .show

View File

@ -16,11 +16,9 @@ final class FollowedTagsViewModel: NSObject {
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>?
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
init(context: AppContext, authenticationBox: MastodonAuthenticationBox) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox) {
self.authenticationBox = authenticationBox
self.followedTags = []

View File

@ -13,10 +13,7 @@ import MastodonUI
import MastodonLocalization
import MastodonSDK
final class FollowerListViewController: UIViewController, NeedsDependency {
weak var context: AppContext!
weak var coordinator: SceneCoordinator!
final class FollowerListViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: FollowerListViewModel
@ -24,10 +21,8 @@ final class FollowerListViewController: UIViewController, NeedsDependency {
let tableView: UITableView
let refreshControl: UIRefreshControl
init(viewModel: FollowerListViewModel, coordinator: SceneCoordinator, context: AppContext) {
init(viewModel: FollowerListViewModel) {
self.context = context
self.coordinator = coordinator
self.viewModel = viewModel
tableView = UITableView()

View File

@ -17,7 +17,6 @@ extension FollowerListViewModel {
) {
diffableDataSource = UserSection.diffableDataSource(
tableView: tableView,
context: context,
authenticationBox: authenticationBox,
userTableViewCellDelegate: userTableViewCellDelegate
)

View File

@ -15,7 +15,6 @@ final class FollowerListViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
@Published var accounts: [Mastodon.Entity.Account]
@Published var relationships: [Mastodon.Entity.Relationship]
@ -43,12 +42,10 @@ final class FollowerListViewModel {
}()
init(
context: AppContext,
authenticationBox: MastodonAuthenticationBox,
domain: String?,
userID: String?
) {
self.context = context
self.authenticationBox = authenticationBox
self.domain = domain
self.userID = userID

View File

@ -14,10 +14,7 @@ import MastodonUI
import CoreDataStack
import MastodonSDK
final class FollowingListViewController: UIViewController, NeedsDependency {
weak var context: AppContext!
weak var coordinator: SceneCoordinator!
final class FollowingListViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: FollowingListViewModel
@ -25,10 +22,8 @@ final class FollowingListViewController: UIViewController, NeedsDependency {
let refreshControl: UIRefreshControl
let tableView: UITableView
init(viewModel: FollowingListViewModel, coordinator: SceneCoordinator, context: AppContext) {
init(viewModel: FollowingListViewModel) {
self.context = context
self.coordinator = coordinator
self.viewModel = viewModel
tableView = UITableView()

View File

@ -18,7 +18,6 @@ extension FollowingListViewModel {
) {
diffableDataSource = UserSection.diffableDataSource(
tableView: tableView,
context: context,
authenticationBox: authenticationBox,
userTableViewCellDelegate: userTableViewCellDelegate
)

View File

@ -16,7 +16,6 @@ final class FollowingListViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
@Published var accounts: [Mastodon.Entity.Account]
@Published var relationships: [Mastodon.Entity.Relationship]
@ -44,12 +43,10 @@ final class FollowingListViewModel {
}()
init(
context: AppContext,
authenticationBox: MastodonAuthenticationBox,
domain: String?,
userID: String?
) {
self.context = context
self.authenticationBox = authenticationBox
self.domain = domain
self.userID = userID

View File

@ -25,14 +25,11 @@ protocol ProfileHeaderViewControllerDelegate: AnyObject {
func profileHeaderViewController(_ profileHeaderViewController: ProfileHeaderViewController, profileHeaderView: ProfileHeaderView, metaTextView: MetaTextView, metaDidPressed meta: Meta)
}
final class ProfileHeaderViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
final class ProfileHeaderViewController: UIViewController, MediaPreviewableViewController {
static let segmentedControlHeight: CGFloat = 50
static let headerMinHeight: CGFloat = segmentedControlHeight
weak var context: AppContext!
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
let viewModel: ProfileHeaderViewModel
@ -82,10 +79,8 @@ final class ProfileHeaderViewController: UIViewController, NeedsDependency, Medi
return documentPickerController
}()
init(context: AppContext, authenticationBox: MastodonAuthenticationBox, coordinator: SceneCoordinator, profileViewModel: ProfileViewModel) {
self.context = context
self.coordinator = coordinator
self.viewModel = ProfileHeaderViewModel(context: context, authenticationBox: authenticationBox, account: profileViewModel.account, me: profileViewModel.me, relationship: profileViewModel.relationship)
init(authenticationBox: MastodonAuthenticationBox, profileViewModel: ProfileViewModel) {
self.viewModel = ProfileHeaderViewModel(authenticationBox: authenticationBox, account: profileViewModel.account, me: profileViewModel.me, relationship: profileViewModel.relationship)
self.profileHeaderView = ProfileHeaderView(account: profileViewModel.account, me: profileViewModel.me, relationship: profileViewModel.relationship)
super.init(nibName: nil, bundle: nil)
@ -341,12 +336,11 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
guard let domain = viewModel.account.domain else { return }
let userID = viewModel.account.id
let followerListViewModel = FollowerListViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
domain: domain,
userID: userID
)
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .follower(viewModel: followerListViewModel),
from: self,
transition: .show
@ -357,12 +351,11 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
let userID = viewModel.account.id
let followingListViewModel = FollowingListViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
domain: domain,
userID: userID
)
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .following(viewModel: followingListViewModel),
from: self,
transition: .show

View File

@ -23,7 +23,6 @@ final class ProfileHeaderViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
@Published var me: Mastodon.Entity.Account
@ -45,8 +44,7 @@ final class ProfileHeaderViewModel {
@Published var isTitleViewDisplaying = false
@Published var isTitleViewContentOffsetSet = false
init(context: AppContext, authenticationBox: MastodonAuthenticationBox, account: Mastodon.Entity.Account, me: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) {
self.context = context
init(authenticationBox: MastodonAuthenticationBox, account: Mastodon.Entity.Account, me: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) {
self.authenticationBox = authenticationBox
self.account = account
self.me = me

View File

@ -22,14 +22,11 @@ protocol ProfileViewModelEditable {
var isEdited: Bool { get }
}
final class ProfileViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
final class ProfileViewController: UIViewController, MediaPreviewableViewController {
public static let containerViewMarginForRegularHorizontalSizeClass: CGFloat = 64
public static let containerViewMarginForCompactHorizontalSizeClass: CGFloat = 16
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
var viewModel: ProfileViewModel? {
@ -144,7 +141,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
}
private func createProfileHeaderViewController(viewModel: ProfileViewModel) -> ProfileHeaderViewController {
let viewController = ProfileHeaderViewController(context: context, authenticationBox: viewModel.authenticationBox, coordinator: coordinator, profileViewModel: viewModel)
let viewController = ProfileHeaderViewController(authenticationBox: viewModel.authenticationBox, profileViewModel: viewModel)
return viewController
}
@ -159,12 +156,6 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
mediaUserTimelineViewModel: viewModel.mediaUserTimelineViewModel,
profileAboutViewModel: viewModel.profileAboutViewModel
)
profilePagingViewModel.viewControllers.forEach { viewController in
if let viewController = viewController as? NeedsDependency {
viewController.context = context
viewController.coordinator = coordinator
}
}
return profilePagingViewModel
}()
return profilePagingViewController
@ -553,15 +544,15 @@ extension ProfileViewController {
switch meta {
case .url(_, _, let url, _):
guard let url = URL(string: url) else { return }
_ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
case .mention(_, _, let userInfo):
guard let href = userInfo?["href"] as? String,
let url = URL(string: href) else { return }
_ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
case .hashtag(_, let hashtag, _):
guard let viewModel = viewModel else { break }
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authenticationBox: viewModel.authenticationBox, hashtag: hashtag)
_ = coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
let hashtagTimelineViewModel = HashtagTimelineViewModel(authenticationBox: viewModel.authenticationBox, hashtag: hashtag)
_ = self.sceneCoordinator?.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
case .email, .emoji:
break
}
@ -578,7 +569,7 @@ extension ProfileViewController {
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
guard let setting = SettingService.shared.currentSetting.value else { return }
_ = coordinator.present(scene: .settings(setting: setting), from: self, transition: .none)
_ = self.sceneCoordinator?.present(scene: .settings(setting: setting), from: self, transition: .none)
}
@objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) {
@ -588,7 +579,7 @@ extension ProfileViewController {
dependency: self,
account: viewModel.account
)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: nil,
@ -602,15 +593,15 @@ extension ProfileViewController {
@objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {
guard let viewModel = viewModel else { return }
let favoriteViewModel = FavoriteViewModel(context: context, authenticationBox: viewModel.authenticationBox)
_ = coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
let favoriteViewModel = FavoriteViewModel(authenticationBox: viewModel.authenticationBox)
_ = self.sceneCoordinator?.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
}
@objc private func bookmarkBarButtonItemPressed(_ sender: UIBarButtonItem) {
guard let viewModel = viewModel else { return }
let bookmarkViewModel = BookmarkViewModel(context: context, authenticationBox: viewModel.authenticationBox)
_ = coordinator.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
let bookmarkViewModel = BookmarkViewModel(authenticationBox: viewModel.authenticationBox)
_ = self.sceneCoordinator?.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
}
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
@ -619,20 +610,19 @@ extension ProfileViewController {
let mention = "@" + viewModel.account.acct
UITextChecker.learnWord(mention)
let composeViewModel = ComposeViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
composeContext: .composeStatus,
destination: .topLevel,
initialContent: mention
)
_ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
_ = self.sceneCoordinator?.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func followedTagsItemPressed(_ sender: UIBarButtonItem) {
guard let viewModel = viewModel else { return }
let followedTagsViewModel = FollowedTagsViewModel(context: context, authenticationBox: viewModel.authenticationBox)
_ = coordinator.present(scene: .followedTags(viewModel: followedTagsViewModel), from: self, transition: .show)
let followedTagsViewModel = FollowedTagsViewModel(authenticationBox: viewModel.authenticationBox)
_ = self.sceneCoordinator?.present(scene: .followedTags(viewModel: followedTagsViewModel), from: self, transition: .show)
}
@objc private func refreshControlValueChanged(_ sender: RefreshControl) {
@ -840,7 +830,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
let alertController = UIAlertController(for: error, title: L10n.Common.Alerts.EditProfileFailure.title, preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
alertController.addAction(okAction)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
@ -900,7 +890,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
alertController.addAction(unblockAction)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
alertController.addAction(cancelAction)
coordinator.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
} else if relationship.domainBlocking {
guard let domain = account.domain else { return }
@ -930,7 +920,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
alertController.addAction(unblockAction)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
alertController.addAction(cancelAction)
coordinator.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
} else if relationship.muting {
let name = account.displayNameWithFallback
@ -950,7 +940,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
alertController.addAction(unmuteAction)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
alertController.addAction(cancelAction)
coordinator.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
self.sceneCoordinator?.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
} else {
Task { [weak self] in
guard let self else { return }

View File

@ -14,10 +14,7 @@ import TabBarPager
import XLPagerTabStrip
import MastodonCore
final class UserTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController, StatusReloadable {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class UserTimelineViewController: UIViewController, MediaPreviewableViewController, StatusReloadable {
var disposeBag = Set<AnyCancellable>()
var viewModel: UserTimelineViewModel!

View File

@ -16,9 +16,7 @@ extension UserTimelineViewModel {
) {
diffableDataSource = StatusSection.diffableDataSource(
tableView: tableView,
context: context,
configuration: StatusSection.Configuration(
context: context,
authenticationBox: authenticationBox,
statusTableViewCellDelegate: statusTableViewCellDelegate,
timelineMiddleLoaderTableViewCellDelegate: nil,

View File

@ -13,9 +13,7 @@ import MastodonLocalization
import MastodonUI
import CoreDataStack
final class FavoritedByViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class FavoritedByViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: UserListViewModel!

View File

@ -13,10 +13,7 @@ import MastodonLocalization
import MastodonUI
import CoreDataStack
final class RebloggedByViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class RebloggedByViewController: UIViewController {
var disposeBag = Set<AnyCancellable>()
var viewModel: UserListViewModel!

View File

@ -19,7 +19,6 @@ extension UserListViewModel {
) {
diffableDataSource = UserSection.diffableDataSource(
tableView: tableView,
context: context,
authenticationBox: authenticationBox,
userTableViewCellDelegate: userTableViewCellDelegate
)

View File

@ -12,13 +12,10 @@ import MastodonAsset
import MastodonCore
import MastodonLocalization
class ReportViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance {
class ReportViewController: UIViewController, ReportViewControllerAppearance {
var disposeBag = Set<AnyCancellable>()
private var observations = Set<NSKeyValueObservation>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
let viewModel: ReportViewModel
@ -50,8 +47,6 @@ class ReportViewController: UIViewController, NeedsDependency, ReportViewControl
viewModel.reportSupplementaryViewModel.delegate = self
let reportReasonViewController = ReportReasonViewController(viewModel: viewModel.reportReasonViewModel)
reportReasonViewController.context = context
reportReasonViewController.coordinator = coordinator
addChild(reportReasonViewController)
reportReasonViewController.view.translatesAutoresizingMaskIntoConstraints = false
@ -80,25 +75,24 @@ extension ReportViewController: ReportReasonViewControllerDelegate {
switch reason {
case .dislike:
let reportResultViewModel = ReportResultViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
account: viewModel.account,
relationship: viewModel.relationship,
isReported: false
)
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .reportResult(viewModel: reportResultViewModel),
from: self,
transition: .show
)
case .violateRule:
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .reportServerRules(viewModel: viewModel.reportServerRulesViewModel),
from: self,
transition: .show
)
case .spam, .other:
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .reportStatus(viewModel: viewModel.reportStatusViewModel),
from: self,
transition: .show
@ -114,7 +108,7 @@ extension ReportViewController: ReportServerRulesViewControllerDelegate {
return
}
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .reportStatus(viewModel: viewModel.reportStatusViewModel),
from: self,
transition: .show
@ -133,7 +127,7 @@ extension ReportViewController: ReportStatusViewControllerDelegate {
}
private func coordinateToReportSupplementary() {
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .reportSupplementary(viewModel: viewModel.reportSupplementaryViewModel),
from: self,
transition: .show
@ -157,14 +151,13 @@ extension ReportViewController: ReportSupplementaryViewControllerDelegate {
let _ = try await viewModel.report()
let reportResultViewModel = ReportResultViewModel(
context: context,
authenticationBox: viewModel.authenticationBox,
account: viewModel.account,
relationship: viewModel.relationship,
isReported: true
)
_ = coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .reportResult(viewModel: reportResultViewModel),
from: self,
transition: .show
@ -174,7 +167,7 @@ extension ReportViewController: ReportSupplementaryViewControllerDelegate {
let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
_ = self.coordinator.present(
_ = self.sceneCoordinator?.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)

View File

@ -17,10 +17,7 @@ protocol ReportReasonViewControllerDelegate: AnyObject {
func reportReasonViewController(_ viewController: ReportReasonViewController, nextButtonPressed button: UIButton)
}
final class ReportReasonViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
final class ReportReasonViewController: UIViewController, ReportViewControllerAppearance {
var disposeBag = Set<AnyCancellable>()
private var observations = Set<NSKeyValueObservation>()

View File

@ -12,13 +12,10 @@ import MastodonAsset
import MastodonCore
import MastodonLocalization
final class ReportResultViewController: UIViewController, NeedsDependency, ReportViewControllerAppearance {
final class ReportResultViewController: UIViewController, ReportViewControllerAppearance {
var disposeBag = Set<AnyCancellable>()
private var observations = Set<NSKeyValueObservation>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: ReportResultViewModel!
private(set) lazy var reportResultView = ReportResultView(viewModel: viewModel)

View File

@ -21,7 +21,6 @@ class ReportResultViewModel: ObservableObject {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authenticationBox: MastodonAuthenticationBox
let account: Mastodon.Entity.Account
var relationship: Mastodon.Entity.Relationship
@ -46,13 +45,11 @@ class ReportResultViewModel: ObservableObject {
let blockActionPublisher = PassthroughSubject<Void, Never>()
init(
context: AppContext,
authenticationBox: MastodonAuthenticationBox,
account: Mastodon.Entity.Account,
relationship: Mastodon.Entity.Relationship,
isReported: Bool
) {
self.context = context
self.authenticationBox = authenticationBox
self.account = account
self.relationship = relationship

Some files were not shown because too many files have changed in this diff Show More