Replace global shared MastodonController instance with (mostly)

dependency injection

The places still using the .shared property are cases where there is no
view controller from which to (easily) get the appropriate instance,
such as user activity and X-Callback-URL handling. These uses will need
to be revisited once there are multiple MastodonControllers.

See #16
This commit is contained in:
Shadowfacts 2020-01-05 15:25:07 -05:00
parent a18bcac8b8
commit 2bdcb9b7f8
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
44 changed files with 286 additions and 157 deletions

View File

@ -118,6 +118,7 @@
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; }; D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; };
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */; }; D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */; };
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */; }; D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */; };
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC19123C271D9000D0238 /* MastodonActivity.swift */; };
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; }; D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; }; D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
@ -388,6 +389,7 @@
D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpinStatusActivity.swift; sourceTree = "<group>"; }; D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpinStatusActivity.swift; sourceTree = "<group>"; };
D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestNotificationTableViewCell.swift; sourceTree = "<group>"; }; D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestNotificationTableViewCell.swift; sourceTree = "<group>"; };
D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowRequestNotificationTableViewCell.xib; sourceTree = "<group>"; }; D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowRequestNotificationTableViewCell.xib; sourceTree = "<group>"; };
D64BC19123C271D9000D0238 /* MastodonActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonActivity.swift; sourceTree = "<group>"; };
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; }; D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; }; D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
@ -1056,6 +1058,7 @@
children = ( children = (
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */, D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */,
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */, D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
D64BC19123C271D9000D0238 /* MastodonActivity.swift */,
D6AEBB4623216B0C00E5038B /* Account Activities */, D6AEBB4623216B0C00E5038B /* Account Activities */,
D627943323A5523800D38C68 /* Status Activities */, D627943323A5523800D38C68 /* Status Activities */,
); );
@ -1670,6 +1673,7 @@
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */, D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */, D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */, D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */,
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */, D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */, D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */, D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,

View File

@ -9,7 +9,7 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
class AccountActivity: UIActivity { class AccountActivity: MastodonActivity {
override class var activityCategory: UIActivity.Category { override class var activityCategory: UIActivity.Category {
return .action return .action

View File

@ -28,7 +28,7 @@ class FollowAccountActivity: AccountActivity {
UIImpactFeedbackGenerator(style: .medium).impactOccurred() UIImpactFeedbackGenerator(style: .medium).impactOccurred()
let request = Account.follow(account.id) let request = Account.follow(account.id)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(relationship, _) = response { if case let .success(relationship, _) = response {
MastodonCache.add(relationship: relationship) MastodonCache.add(relationship: relationship)
} else { } else {

View File

@ -28,7 +28,7 @@ class SendMessageActivity: AccountActivity {
override var activityViewController: UIViewController? { override var activityViewController: UIViewController? {
guard let account = account else { return nil } guard let account = account else { return nil }
return UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct)) return UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, mastodonController: mastodonController))
} }
} }

View File

@ -28,7 +28,7 @@ class UnfollowAccountActivity: AccountActivity {
UIImpactFeedbackGenerator(style: .medium).impactOccurred() UIImpactFeedbackGenerator(style: .medium).impactOccurred()
let request = Account.unfollow(account.id) let request = Account.unfollow(account.id)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(relationship, _) = response { if case let .success(relationship, _) = response {
MastodonCache.add(relationship: relationship) MastodonCache.add(relationship: relationship)
} else { } else {

View File

@ -0,0 +1,15 @@
//
// MastodonActivity.swift
// Tusker
//
// Created by Shadowfacts on 1/5/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
class MastodonActivity: UIActivity {
var mastodonController: MastodonController {
MastodonController.shared
}
}

View File

@ -27,7 +27,7 @@ class BookmarkStatusActivity: StatusActivity {
guard let status = status else { return } guard let status = status else { return }
let request = Status.bookmark(status) let request = Status.bookmark(status)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(status, _) = response { if case let .success(status, _) = response {
MastodonCache.add(status: status) MastodonCache.add(status: status)
} else { } else {

View File

@ -26,7 +26,7 @@ class PinStatusActivity: StatusActivity {
guard let status = status else { return } guard let status = status else { return }
let request = Status.pin(status) let request = Status.pin(status)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(status, _) = response { if case let .success(status, _) = response {
MastodonCache.add(status: status) MastodonCache.add(status: status)
} else { } else {

View File

@ -9,7 +9,7 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
class StatusActivity: UIActivity { class StatusActivity: MastodonActivity {
override class var activityCategory: UIActivity.Category { override class var activityCategory: UIActivity.Category {
return .action return .action

View File

@ -27,7 +27,7 @@ class UnbookmarkStatusActivity: StatusActivity {
guard let status = status else { return } guard let status = status else { return }
let request = Status.unbookmark(status) let request = Status.unbookmark(status)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(status, _) = response { if case let .success(status, _) = response {
MastodonCache.add(status: status) MastodonCache.add(status: status)
} else { } else {

View File

@ -26,7 +26,7 @@ class UnpinStatusActivity: StatusActivity {
guard let status = status else { return } guard let status = status else { return }
let request = Status.unpin(status) let request = Status.unpin(status)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(status, _) = response { if case let .success(status, _) = response {
MastodonCache.add(status: status) MastodonCache.add(status: status)
} else { } else {

View File

@ -13,6 +13,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
let mastodonController = MastodonController.shared
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppShortcutItem.createItems(for: application) AppShortcutItem.createItems(for: application)
@ -95,11 +97,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
} }
func showAppUI() { func showAppUI() {
MastodonController.createClient() mastodonController.createClient()
MastodonController.getOwnAccount() mastodonController.getOwnAccount()
MastodonController.getOwnInstance() mastodonController.getOwnInstance()
let tabBarController = MainTabBarViewController() let tabBarController = MainTabBarViewController(mastodonController: mastodonController)
window!.rootViewController = tabBarController window!.rootViewController = tabBarController
} }

View File

@ -11,32 +11,33 @@ import Pachyderm
class MastodonController { class MastodonController {
private static var client: Client! @available(*, deprecated, message: "Use dependency injection to obtain an instance")
static let shared = MastodonController()
static var account: Account! private var client: Client!
static var instance: Instance!
static var accessToken: String? { var account: Account!
var instance: Instance!
var accessToken: String? {
client?.accessToken client?.accessToken
} }
private init() {} func createClient(instanceURL: URL = LocalData.shared.instanceURL!) {
client = Client(baseURL: instanceURL)
static func createClient() { if instanceURL == LocalData.shared.instanceURL {
guard let url = LocalData.shared.instanceURL else { fatalError("Can't connect without instance URL") } client.clientID = LocalData.shared.clientID
client.clientSecret = LocalData.shared.clientSecret
client = Client(baseURL: url) client.accessToken = LocalData.shared.accessToken
}
client.clientID = LocalData.shared.clientID
client.clientSecret = LocalData.shared.clientSecret
client.accessToken = LocalData.shared.accessToken
} }
static func run<Result>(_ request: Request<Result>, completion: @escaping Client.Callback<Result>) { func run<Result>(_ request: Request<Result>, completion: @escaping Client.Callback<Result>) {
client.run(request, completion: completion) client.run(request, completion: completion)
} }
static func registerApp(completion: @escaping () -> Void) { func registerApp(completion: @escaping () -> Void) {
guard LocalData.shared.clientID == nil, guard LocalData.shared.clientID == nil,
LocalData.shared.clientSecret == nil else { LocalData.shared.clientSecret == nil else {
completion() completion()
@ -51,7 +52,7 @@ class MastodonController {
} }
} }
static func authorize(authorizationCode: String, completion: @escaping () -> Void) { func authorize(authorizationCode: String, completion: @escaping () -> Void) {
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
guard case let .success(settings, _) = response else { fatalError() } guard case let .success(settings, _) = response else { fatalError() }
LocalData.shared.accessToken = settings.accessToken LocalData.shared.accessToken = settings.accessToken
@ -59,7 +60,7 @@ class MastodonController {
} }
} }
static func getOwnAccount(completion: ((Account) -> Void)? = nil) { func getOwnAccount(completion: ((Account) -> Void)? = nil) {
if account != nil { if account != nil {
completion?(account) completion?(account)
} else { } else {
@ -73,7 +74,7 @@ class MastodonController {
} }
} }
static func getOwnInstance() { func getOwnInstance() {
let request = Client.getInstance() let request = Client.getInstance()
run(request) { (response) in run(request) { (response) in
guard case let .success(instance, _) = response else { fatalError() } guard case let .success(instance, _) = response else { fatalError() }

View File

@ -20,6 +20,8 @@ class MastodonCache {
static let statusSubject = PassthroughSubject<Status, Never>() static let statusSubject = PassthroughSubject<Status, Never>()
static let accountSubject = PassthroughSubject<Account, Never>() static let accountSubject = PassthroughSubject<Account, Never>()
static var mastodonController: MastodonController { .shared }
// MARK: - Statuses // MARK: - Statuses
static func status(for id: String) -> Status? { static func status(for id: String) -> Status? {
return statuses[id] return statuses[id]
@ -38,7 +40,7 @@ class MastodonCache {
static func status(for id: String, completion: @escaping (Status?) -> Void) { static func status(for id: String, completion: @escaping (Status?) -> Void) {
let request = Client.getStatus(id: id) let request = Client.getStatus(id: id)
MastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(status, _) = response else { guard case let .success(status, _) = response else {
completion(nil) completion(nil)
return return
@ -68,7 +70,7 @@ class MastodonCache {
static func account(for id: String, completion: @escaping (Account?) -> Void) { static func account(for id: String, completion: @escaping (Account?) -> Void) {
let request = Client.getAccount(id: id) let request = Client.getAccount(id: id)
MastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(account, _) = response else { guard case let .success(account, _) = response else {
completion(nil) completion(nil)
return return
@ -97,7 +99,7 @@ class MastodonCache {
static func relationship(for id: String, completion: @escaping (Relationship?) -> Void) { static func relationship(for id: String, completion: @escaping (Relationship?) -> Void) {
let request = Client.getRelationships(accounts: [id]) let request = Client.getRelationships(accounts: [id])
MastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(relationships, _) = response, guard case let .success(relationships, _) = response,
let relationship = relationships.first else { let relationship = relationships.first else {
completion(nil) completion(nil)

View File

@ -12,10 +12,13 @@ class AccountListTableViewController: EnhancedTableViewController {
private let accountCell = "accountCell" private let accountCell = "accountCell"
let mastodonController: MastodonController
let accountIDs: [String] let accountIDs: [String]
init(accountIDs: [String]) { init(accountIDs: [String], mastodonController: MastodonController) {
self.accountIDs = accountIDs self.accountIDs = accountIDs
self.mastodonController = mastodonController
super.init(style: .grouped) super.init(style: .grouped)
} }
@ -58,4 +61,6 @@ class AccountListTableViewController: EnhancedTableViewController {
} }
extension AccountListTableViewController: TuskerNavigationDelegate {} extension AccountListTableViewController: TuskerNavigationDelegate {
var apiController: MastodonController { mastodonController }
}

View File

@ -13,6 +13,8 @@ class BookmarksTableViewController: EnhancedTableViewController {
private let statusCell = "statusCell" private let statusCell = "statusCell"
let mastodonController: MastodonController
var statuses: [(id: String, state: StatusState)] = [] { var statuses: [(id: String, state: StatusState)] = [] {
didSet { didSet {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -24,7 +26,9 @@ class BookmarksTableViewController: EnhancedTableViewController {
var newer: RequestRange? var newer: RequestRange?
var older: RequestRange? var older: RequestRange?
init() { init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
super.init(style: .plain) super.init(style: .plain)
title = NSLocalizedString("Bookmarks", comment: "bookmarks screen title") title = NSLocalizedString("Bookmarks", comment: "bookmarks screen title")
@ -45,7 +49,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
tableView.prefetchDataSource = self tableView.prefetchDataSource = self
let request = Client.getBookmarks() let request = Client.getBookmarks()
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(statuses, pagination) = response else { fatalError() } guard case let .success(statuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: statuses) MastodonCache.addAll(statuses: statuses)
self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) }) self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) })
@ -82,7 +86,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
} }
let request = Client.getBookmarks(range: older) let request = Client.getBookmarks(range: older)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(newStatuses, pagination) = response else { fatalError() } guard case let .success(newStatuses, pagination) = response else { fatalError() }
self.older = pagination?.older self.older = pagination?.older
MastodonCache.addAll(statuses: newStatuses) MastodonCache.addAll(statuses: newStatuses)
@ -107,7 +111,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
let unbookmarkAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Unbookmark", comment: "unbookmark action title")) { (action, view, completion) in let unbookmarkAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Unbookmark", comment: "unbookmark action title")) { (action, view, completion) in
let request = Status.unbookmark(status) let request = Status.unbookmark(status)
MastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
guard case let .success(newStatus, _) = response else { fatalError() } guard case let .success(newStatus, _) = response else { fatalError() }
MastodonCache.add(status: newStatus) MastodonCache.add(status: newStatus)
self.statuses.remove(at: indexPath.row) self.statuses.remove(at: indexPath.row)
@ -131,7 +135,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
return [ return [
UIAction(title: NSLocalizedString("Unbookmark", comment: "unbookmark action title"), image: UIImage(systemName: "bookmark.fill"), identifier: .init("unbookmark"), discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in UIAction(title: NSLocalizedString("Unbookmark", comment: "unbookmark action title"), image: UIImage(systemName: "bookmark.fill"), identifier: .init("unbookmark"), discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
let request = Status.unbookmark(status) let request = Status.unbookmark(status)
MastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
guard case let .success(newStatus, _) = response else { fatalError() } guard case let .success(newStatus, _) = response else { fatalError() }
MastodonCache.add(status: newStatus) MastodonCache.add(status: newStatus)
self.statuses.remove(at: indexPath.row) self.statuses.remove(at: indexPath.row)
@ -143,6 +147,8 @@ class BookmarksTableViewController: EnhancedTableViewController {
} }
extension BookmarksTableViewController: StatusTableViewCellDelegate { extension BookmarksTableViewController: StatusTableViewCellDelegate {
var apiController: MastodonController { mastodonController }
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
tableView.beginUpdates() tableView.beginUpdates()
tableView.endUpdates() tableView.endUpdates()

View File

@ -12,6 +12,8 @@ import Intents
class ComposeViewController: UIViewController { class ComposeViewController: UIViewController {
let mastodonController: MastodonController
var inReplyToID: String? var inReplyToID: String?
var accountsToMention: [String] var accountsToMention: [String]
var initialText: String? var initialText: String?
@ -64,7 +66,9 @@ class ComposeViewController: UIViewController {
@IBOutlet weak var postProgressView: SteppedProgressView! @IBOutlet weak var postProgressView: SteppedProgressView!
init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil) { init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil, mastodonController: MastodonController) {
self.mastodonController = mastodonController
self.inReplyToID = inReplyToID self.inReplyToID = inReplyToID
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) { if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct } accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct }
@ -73,7 +77,7 @@ class ComposeViewController: UIViewController {
} else { } else {
accountsToMention = [] accountsToMention = []
} }
if let ownAccount = MastodonController.account { if let ownAccount = mastodonController.account {
accountsToMention.removeAll(where: { acct in ownAccount.acct == acct }) accountsToMention.removeAll(where: { acct in ownAccount.acct == acct })
} }
accountsToMention = accountsToMention.uniques() accountsToMention = accountsToMention.uniques()
@ -120,7 +124,7 @@ class ComposeViewController: UIViewController {
statusTextView.text = accountsToMention.map({ acct in "@\(acct) " }).joined() statusTextView.text = accountsToMention.map({ acct in "@\(acct) " }).joined()
initialText = statusTextView.text initialText = statusTextView.text
MastodonController.getOwnAccount { (account) in mastodonController.getOwnAccount { (account) in
DispatchQueue.main.async { DispatchQueue.main.async {
self.selfDetailView.update(account: account) self.selfDetailView.update(account: account)
} }
@ -270,7 +274,7 @@ class ComposeViewController: UIViewController {
// TODO: include CW char count // TODO: include CW char count
let count = CharacterCounter.count(text: statusTextView.text) let count = CharacterCounter.count(text: statusTextView.text)
let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0 let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0
let remaining = (MastodonController.instance.maxStatusCharacters ?? 500) - count - cwCount let remaining = (mastodonController.instance.maxStatusCharacters ?? 500) - count - cwCount
if remaining < 0 { if remaining < 0 {
charactersRemainingLabel.textColor = .red charactersRemainingLabel.textColor = .red
postBarButtonItem.isEnabled = false postBarButtonItem.isEnabled = false
@ -296,7 +300,7 @@ class ComposeViewController: UIViewController {
} }
func updateAddAttachmentButton() { func updateAddAttachmentButton() {
switch MastodonController.instance.instanceType { switch mastodonController.instance.instanceType {
case .pleroma: case .pleroma:
addAttachmentButton.isEnabled = true addAttachmentButton.isEnabled = true
case .mastodon: case .mastodon:
@ -481,7 +485,7 @@ class ComposeViewController: UIViewController {
self.postProgressView.step() self.postProgressView.step()
let request = Client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description) let request = Client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
MastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
guard case let .success(attachment, _) = response else { fatalError() } guard case let .success(attachment, _) = response else { fatalError() }
attachments[index] = attachment attachments[index] = attachment
@ -507,7 +511,7 @@ class ComposeViewController: UIViewController {
spoilerText: contentWarning, spoilerText: contentWarning,
visibility: visibility, visibility: visibility,
language: nil) language: nil)
MastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
guard case let .success(status, _) = response else { fatalError() } guard case let .success(status, _) = response else { fatalError() }
self.postedStatus = status self.postedStatus = status
MastodonCache.add(status: status) MastodonCache.add(status: status)
@ -520,7 +524,7 @@ class ComposeViewController: UIViewController {
self.postProgressView.step() self.postProgressView.step()
self.dismiss(animated: true) self.dismiss(animated: true)
let conversationVC = ConversationTableViewController(for: status.id) let conversationVC = ConversationTableViewController(for: status.id, mastodonController: self.mastodonController)
self.show(conversationVC, sender: self) self.show(conversationVC, sender: self)
self.xcbSession?.complete(with: .success, additionalData: [ self.xcbSession?.complete(with: .success, additionalData: [
@ -561,7 +565,7 @@ extension ComposeViewController: UITextViewDelegate {
extension ComposeViewController: AssetPickerViewControllerDelegate { extension ComposeViewController: AssetPickerViewControllerDelegate {
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool { func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool {
switch MastodonController.instance.instanceType { switch mastodonController.instance.instanceType {
case .pleroma: case .pleroma:
return true return true
case .mastodon: case .mastodon:

View File

@ -15,6 +15,8 @@ class ConversationTableViewController: EnhancedTableViewController {
static let showPostsImage = UIImage(systemName: "eye.fill")! static let showPostsImage = UIImage(systemName: "eye.fill")!
static let hidePostsImage = UIImage(systemName: "eye.slash.fill")! static let hidePostsImage = UIImage(systemName: "eye.slash.fill")!
let mastodonController: MastodonController
let mainStatusID: String let mainStatusID: String
let mainStatusState: StatusState let mainStatusState: StatusState
var statuses: [(id: String, state: StatusState)] = [] { var statuses: [(id: String, state: StatusState)] = [] {
@ -28,9 +30,10 @@ class ConversationTableViewController: EnhancedTableViewController {
var showStatusesAutomatically = false var showStatusesAutomatically = false
var visibilityBarButtonItem: UIBarButtonItem! var visibilityBarButtonItem: UIBarButtonItem!
init(for mainStatusID: String, state: StatusState = .unknown) { init(for mainStatusID: String, state: StatusState = .unknown, mastodonController: MastodonController) {
self.mainStatusID = mainStatusID self.mainStatusID = mainStatusID
self.mainStatusState = state self.mainStatusState = state
self.mastodonController = mastodonController
super.init(style: .plain) super.init(style: .plain)
} }
@ -58,7 +61,7 @@ class ConversationTableViewController: EnhancedTableViewController {
guard let mainStatus = MastodonCache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID)") } guard let mainStatus = MastodonCache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID)") }
let request = Status.getContext(mainStatus) let request = Status.getContext(mainStatus)
MastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(context, _) = response else { fatalError() } guard case let .success(context, _) = response else { fatalError() }
let parents = self.getDirectParents(of: mainStatus, from: context.ancestors) let parents = self.getDirectParents(of: mainStatus, from: context.ancestors)
MastodonCache.addAll(statuses: parents) MastodonCache.addAll(statuses: parents)
@ -155,6 +158,7 @@ class ConversationTableViewController: EnhancedTableViewController {
} }
extension ConversationTableViewController: StatusTableViewCellDelegate { extension ConversationTableViewController: StatusTableViewCellDelegate {
var apiController: MastodonController { mastodonController }
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights // causes the table view to recalculate the cell heights
tableView.beginUpdates() tableView.beginUpdates()

View File

@ -12,12 +12,16 @@ import Pachyderm
class ExploreViewController: EnhancedTableViewController { class ExploreViewController: EnhancedTableViewController {
let mastodonController: MastodonController
var dataSource: DataSource! var dataSource: DataSource!
var resultsController: SearchResultsViewController! var resultsController: SearchResultsViewController!
var searchController: UISearchController! var searchController: UISearchController!
init() { init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
super.init(style: .insetGrouped) super.init(style: .insetGrouped)
title = NSLocalizedString("Explore", comment: "explore tab title") title = NSLocalizedString("Explore", comment: "explore tab title")
@ -88,7 +92,7 @@ class ExploreViewController: EnhancedTableViewController {
dataSource.apply(snapshot) dataSource.apply(snapshot)
} }
resultsController = SearchResultsViewController() resultsController = SearchResultsViewController(mastodonController: mastodonController)
resultsController.exploreNavigationController = self.navigationController! resultsController.exploreNavigationController = self.navigationController!
searchController = UISearchController(searchResultsController: resultsController) searchController = UISearchController(searchResultsController: resultsController)
searchController.searchResultsUpdater = resultsController searchController.searchResultsUpdater = resultsController
@ -107,7 +111,7 @@ class ExploreViewController: EnhancedTableViewController {
func reloadLists() { func reloadLists() {
let request = Client.getLists() let request = Client.getLists()
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(lists, _) = response else { guard case let .success(lists, _) = response else {
fatalError() fatalError()
} }
@ -143,7 +147,7 @@ class ExploreViewController: EnhancedTableViewController {
alert.addAction(UIAlertAction(title: NSLocalizedString("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in alert.addAction(UIAlertAction(title: NSLocalizedString("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in
let request = List.delete(list) let request = List.delete(list)
MastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
guard case .success(_, _) = response else { guard case .success(_, _) = response else {
fatalError() fatalError()
} }
@ -174,10 +178,10 @@ class ExploreViewController: EnhancedTableViewController {
return return
case .bookmarks: case .bookmarks:
show(BookmarksTableViewController(), sender: nil) show(BookmarksTableViewController(mastodonController: mastodonController), sender: nil)
case let .list(list): case let .list(list):
show(ListTimelineViewController(for: list), sender: nil) show(ListTimelineViewController(for: list, mastodonController: mastodonController), sender: nil)
case .addList: case .addList:
tableView.selectRow(at: nil, animated: true, scrollPosition: .none) tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
@ -190,13 +194,13 @@ class ExploreViewController: EnhancedTableViewController {
} }
let request = Client.createList(title: title) let request = Client.createList(title: title)
MastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
guard case let .success(list, _) = response else { fatalError() } guard case let .success(list, _) = response else { fatalError() }
self.reloadLists() self.reloadLists()
DispatchQueue.main.async { DispatchQueue.main.async {
let listTimelineController = ListTimelineViewController(for: list) let listTimelineController = ListTimelineViewController(for: list, mastodonController: self.mastodonController)
listTimelineController.presentEditOnAppear = true listTimelineController.presentEditOnAppear = true
self.show(listTimelineController, sender: nil) self.show(listTimelineController, sender: nil)
} }
@ -205,11 +209,11 @@ class ExploreViewController: EnhancedTableViewController {
present(alert, animated: true) present(alert, animated: true)
case let .savedHashtag(hashtag): case let .savedHashtag(hashtag):
show(HashtagTimelineViewController(for: hashtag), sender: nil) show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
case .addSavedHashtag: case .addSavedHashtag:
tableView.selectRow(at: nil, animated: true, scrollPosition: .none) tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
let navController = UINavigationController(rootViewController: AddSavedHashtagViewController()) let navController = UINavigationController(rootViewController: AddSavedHashtagViewController(mastodonController: mastodonController))
present(navController, animated: true) present(navController, animated: true)
case let .savedInstance(url): case let .savedInstance(url):

View File

@ -11,6 +11,8 @@ import Pachyderm
class EditListAccountsViewController: EnhancedTableViewController { class EditListAccountsViewController: EnhancedTableViewController {
let mastodonController: MastodonController
let list: List let list: List
var dataSource: DataSource! var dataSource: DataSource!
@ -20,8 +22,9 @@ class EditListAccountsViewController: EnhancedTableViewController {
var searchResultsController: SearchResultsViewController! var searchResultsController: SearchResultsViewController!
var searchController: UISearchController! var searchController: UISearchController!
init(list: List) { init(list: List, mastodonController: MastodonController) {
self.list = list self.list = list
self.mastodonController = mastodonController
super.init(style: .plain) super.init(style: .plain)
@ -49,7 +52,7 @@ class EditListAccountsViewController: EnhancedTableViewController {
}) })
dataSource.editListAccountsController = self dataSource.editListAccountsController = self
searchResultsController = SearchResultsViewController() searchResultsController = SearchResultsViewController(mastodonController: mastodonController)
searchResultsController.delegate = self searchResultsController.delegate = self
searchResultsController.onlySections = [.accounts] searchResultsController.onlySections = [.accounts]
searchController = UISearchController(searchResultsController: searchResultsController) searchController = UISearchController(searchResultsController: searchResultsController)
@ -70,7 +73,7 @@ class EditListAccountsViewController: EnhancedTableViewController {
func loadAccounts() { func loadAccounts() {
let request = List.getAccounts(list) let request = List.getAccounts(list)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(accounts, pagination) = response else { guard case let .success(accounts, pagination) = response else {
fatalError() fatalError()
} }
@ -109,7 +112,7 @@ class EditListAccountsViewController: EnhancedTableViewController {
fatalError() fatalError()
} }
let request = List.update(self.list, title: text) let request = List.update(self.list, title: text)
MastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
guard case .success(_, _) = response else { guard case .success(_, _) = response else {
fatalError() fatalError()
} }
@ -143,7 +146,7 @@ extension EditListAccountsViewController {
} }
let request = List.remove(editListAccountsController!.list, accounts: [id]) let request = List.remove(editListAccountsController!.list, accounts: [id])
MastodonController.run(request) { (response) in editListAccountsController!.mastodonController.run(request) { (response) in
guard case .success(_, _) = response else { guard case .success(_, _) = response else {
fatalError() fatalError()
} }
@ -157,7 +160,7 @@ extension EditListAccountsViewController {
extension EditListAccountsViewController: SearchResultsViewControllerDelegate { extension EditListAccountsViewController: SearchResultsViewControllerDelegate {
func selectedSearchResult(account accountID: String) { func selectedSearchResult(account accountID: String) {
let request = List.add(list, accounts: [accountID]) let request = List.add(list, accounts: [accountID])
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case .success(_, _) = response else { guard case .success(_, _) = response else {
fatalError() fatalError()
} }

View File

@ -15,10 +15,10 @@ class ListTimelineViewController: TimelineTableViewController {
var presentEditOnAppear = false var presentEditOnAppear = false
init(for list: List) { init(for list: List, mastodonController: MastodonController) {
self.list = list self.list = list
super.init(for: .list(id: list.id)) super.init(for: .list(id: list.id), mastodonController: mastodonController)
title = list.title title = list.title
} }
@ -42,7 +42,7 @@ class ListTimelineViewController: TimelineTableViewController {
} }
func presentEdit(animated: Bool) { func presentEdit(animated: Bool) {
let editListAccountsController = EditListAccountsViewController(list: list) let editListAccountsController = EditListAccountsViewController(list: list, mastodonController: mastodonController)
editListAccountsController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed)) editListAccountsController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed))
let navController = UINavigationController(rootViewController: editListAccountsController) let navController = UINavigationController(rootViewController: editListAccountsController)
present(navController, animated: animated) present(navController, animated: animated)

View File

@ -10,17 +10,29 @@ import UIKit
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate { class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
let mastodonController: MastodonController
init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.delegate = self self.delegate = self
viewControllers = [ viewControllers = [
embedInNavigationController(TimelinesPageViewController()), embedInNavigationController(TimelinesPageViewController(mastodonController: mastodonController)),
embedInNavigationController(NotificationsPageViewController()), embedInNavigationController(NotificationsPageViewController(mastodonController: mastodonController)),
ComposeViewController(), ComposeViewController(mastodonController: mastodonController),
embedInNavigationController(ExploreViewController()), embedInNavigationController(ExploreViewController(mastodonController: mastodonController)),
embedInNavigationController(MyProfileTableViewController()), embedInNavigationController(MyProfileTableViewController(mastodonController: mastodonController)),
] ]
} }
@ -41,7 +53,7 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
} }
func presentCompose() { func presentCompose() {
let compose = ComposeViewController() let compose = ComposeViewController(mastodonController: mastodonController)
let navigationController = embedInNavigationController(compose) let navigationController = embedInNavigationController(compose)
navigationController.presentationController?.delegate = compose navigationController.presentationController?.delegate = compose
present(navigationController, animated: true) present(navigationController, animated: true)

View File

@ -14,12 +14,16 @@ class NotificationsPageViewController: SegmentedPageViewController {
private let notificationsTitle = NSLocalizedString("Notifications", comment: "notifications tab title") private let notificationsTitle = NSLocalizedString("Notifications", comment: "notifications tab title")
private let mentionsTitle = NSLocalizedString("Mentions", comment: "mentions tab title") private let mentionsTitle = NSLocalizedString("Mentions", comment: "mentions tab title")
init() { let mastodonController: MastodonController
let notifications = NotificationsTableViewController(allowedTypes: Pachyderm.Notification.Kind.allCases)
init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
let notifications = NotificationsTableViewController(allowedTypes: Pachyderm.Notification.Kind.allCases, mastodonController: mastodonController)
notifications.title = notificationsTitle notifications.title = notificationsTitle
notifications.userActivity = UserActivityManager.checkNotificationsActivity() notifications.userActivity = UserActivityManager.checkNotificationsActivity()
let mentions = NotificationsTableViewController(allowedTypes: [.mention]) let mentions = NotificationsTableViewController(allowedTypes: [.mention], mastodonController: mastodonController)
mentions.title = mentionsTitle mentions.title = mentionsTitle
mentions.userActivity = UserActivityManager.checkMentionsActivity() mentions.userActivity = UserActivityManager.checkMentionsActivity()

View File

@ -16,6 +16,8 @@ class NotificationsTableViewController: EnhancedTableViewController {
private let followGroupCell = "followGroupCell" private let followGroupCell = "followGroupCell"
private let followRequestCell = "followRequestCell" private let followRequestCell = "followRequestCell"
let mastodonController: MastodonController
let excludedTypes: [Pachyderm.Notification.Kind] let excludedTypes: [Pachyderm.Notification.Kind]
let groupTypes = [Notification.Kind.favourite, .reblog, .follow] let groupTypes = [Notification.Kind.favourite, .reblog, .follow]
@ -30,8 +32,9 @@ class NotificationsTableViewController: EnhancedTableViewController {
var newer: RequestRange? var newer: RequestRange?
var older: RequestRange? var older: RequestRange?
init(allowedTypes: [Pachyderm.Notification.Kind]) { init(allowedTypes: [Pachyderm.Notification.Kind], mastodonController: MastodonController) {
self.excludedTypes = Array(Set(Pachyderm.Notification.Kind.allCases).subtracting(allowedTypes)) self.excludedTypes = Array(Set(Pachyderm.Notification.Kind.allCases).subtracting(allowedTypes))
self.mastodonController = mastodonController
super.init(style: .plain) super.init(style: .plain)
@ -57,7 +60,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
tableView.prefetchDataSource = self tableView.prefetchDataSource = self
let request = Client.getNotifications(excludeTypes: excludedTypes) let request = Client.getNotifications(excludeTypes: excludedTypes)
MastodonController.run(request) { result in mastodonController.run(request) { result in
guard case let .success(notifications, pagination) = result else { fatalError() } guard case let .success(notifications, pagination) = result else { fatalError() }
let groups = NotificationGroup.createGroups(notifications: notifications, only: self.groupTypes) let groups = NotificationGroup.createGroups(notifications: notifications, only: self.groupTypes)
@ -125,7 +128,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
guard let older = older else { return } guard let older = older else { return }
let request = Client.getNotifications(excludeTypes: excludedTypes, range: older) let request = Client.getNotifications(excludeTypes: excludedTypes, range: older)
MastodonController.run(request) { result in mastodonController.run(request) { result in
guard case let .success(newNotifications, pagination) = result else { fatalError() } guard case let .success(newNotifications, pagination) = result else { fatalError() }
let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes) let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes)
@ -182,7 +185,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
.map(Pachyderm.Notification.dismiss(id:)) .map(Pachyderm.Notification.dismiss(id:))
.forEach { (request) in .forEach { (request) in
group.enter() group.enter()
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
group.leave() group.leave()
} }
} }
@ -197,7 +200,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
guard let newer = newer else { return } guard let newer = newer else { return }
let request = Client.getNotifications(excludeTypes: excludedTypes, range: newer) let request = Client.getNotifications(excludeTypes: excludedTypes, range: newer)
MastodonController.run(request) { result in mastodonController.run(request) { result in
guard case let .success(newNotifications, pagination) = result else { fatalError() } guard case let .success(newNotifications, pagination) = result else { fatalError() }
let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes) let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes)
@ -222,6 +225,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
} }
extension NotificationsTableViewController: StatusTableViewCellDelegate { extension NotificationsTableViewController: StatusTableViewCellDelegate {
var apiController: MastodonController { mastodonController }
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights // causes the table view to recalculate the cell heights
tableView.beginUpdates() tableView.beginUpdates()

View File

@ -46,8 +46,9 @@ class OnboardingViewController: UINavigationController {
extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate { extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate {
func didSelectInstance(url: URL) { func didSelectInstance(url: URL) {
LocalData.shared.instanceURL = url LocalData.shared.instanceURL = url
MastodonController.createClient() let mastodonController = MastodonController.shared
MastodonController.registerApp { mastodonController.createClient()
mastodonController.registerApp {
let clientID = LocalData.shared.clientID! let clientID = LocalData.shared.clientID!
let callbackURL = "tusker://oauth" let callbackURL = "tusker://oauth"
@ -69,7 +70,7 @@ extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate
let item = components.queryItems?.first(where: { $0.name == "code" }), let item = components.queryItems?.first(where: { $0.name == "code" }),
let authCode = item.value else { return } let authCode = item.value else { return }
MastodonController.authorize(authorizationCode: authCode) { mastodonController.authorize(authorizationCode: authCode) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.onboardingDelegate?.didFinishOnboarding() self.onboardingDelegate?.didFinishOnboarding()
} }

View File

@ -11,14 +11,14 @@ import SwiftUI
class MyProfileTableViewController: ProfileTableViewController { class MyProfileTableViewController: ProfileTableViewController {
init() { init(mastodonController: MastodonController) {
super.init(accountID: nil) super.init(accountID: nil, mastodonController: mastodonController)
title = "My Profile" title = "My Profile"
tabBarItem.image = UIImage(systemName: "person.fill") tabBarItem.image = UIImage(systemName: "person.fill")
MastodonController.getOwnAccount { (account) in mastodonController.getOwnAccount { (account) in
self.accountID = account.id self.accountID = account.id
ImageCache.avatars.get(account.avatar, completion: { (data) in ImageCache.avatars.get(account.avatar, completion: { (data) in

View File

@ -12,6 +12,8 @@ import SafariServices
class ProfileTableViewController: EnhancedTableViewController { class ProfileTableViewController: EnhancedTableViewController {
let mastodonController: MastodonController
var accountID: String! { var accountID: String! {
didSet { didSet {
if shouldLoadOnAccountIDSet { if shouldLoadOnAccountIDSet {
@ -43,7 +45,9 @@ class ProfileTableViewController: EnhancedTableViewController {
var shouldLoadOnAccountIDSet = false var shouldLoadOnAccountIDSet = false
var loadingVC: LoadingViewController? = nil var loadingVC: LoadingViewController? = nil
init(accountID: String?) { init(accountID: String?, mastodonController: MastodonController) {
self.mastodonController = mastodonController
self.accountID = accountID self.accountID = accountID
super.init(style: .plain) super.init(style: .plain)
@ -130,12 +134,12 @@ class ProfileTableViewController: EnhancedTableViewController {
func getStatuses(for range: RequestRange = .default, onlyPinned: Bool = false, completion: @escaping Client.Callback<[Status]>) { func getStatuses(for range: RequestRange = .default, onlyPinned: Bool = false, completion: @escaping Client.Callback<[Status]>) {
let request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: onlyPinned, excludeReplies: !Preferences.shared.showRepliesInProfiles) let request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: onlyPinned, excludeReplies: !Preferences.shared.showRepliesInProfiles)
MastodonController.run(request, completion: completion) mastodonController.run(request, completion: completion)
} }
func sendMessageMentioning() { func sendMessageMentioning() {
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") }
let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct)) let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, mastodonController: mastodonController))
present(vc, animated: true) present(vc, animated: true)
} }
@ -233,6 +237,8 @@ class ProfileTableViewController: EnhancedTableViewController {
} }
extension ProfileTableViewController: StatusTableViewCellDelegate { extension ProfileTableViewController: StatusTableViewCellDelegate {
var apiController: MastodonController { mastodonController }
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights // causes the table view to recalculate the cell heights
tableView.beginUpdates() tableView.beginUpdates()

View File

@ -28,6 +28,8 @@ extension SearchResultsViewControllerDelegate {
class SearchResultsViewController: EnhancedTableViewController { class SearchResultsViewController: EnhancedTableViewController {
let mastodonController: MastodonController!
weak var exploreNavigationController: UINavigationController? weak var exploreNavigationController: UINavigationController?
weak var delegate: SearchResultsViewControllerDelegate? weak var delegate: SearchResultsViewControllerDelegate?
@ -40,7 +42,9 @@ class SearchResultsViewController: EnhancedTableViewController {
let searchSubject = PassthroughSubject<String?, Never>() let searchSubject = PassthroughSubject<String?, Never>()
var currentQuery: String? var currentQuery: String?
init() { init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
super.init(style: .grouped) super.init(style: .grouped)
title = NSLocalizedString("Search", comment: "search screen title") title = NSLocalizedString("Search", comment: "search screen title")
@ -118,7 +122,7 @@ class SearchResultsViewController: EnhancedTableViewController {
} }
let request = Client.search(query: query, resolve: true, limit: 10) let request = Client.search(query: query, resolve: true, limit: 10)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(results, _) = response else { fatalError() } guard case let .success(results, _) = response else { fatalError() }
DispatchQueue.main.async { DispatchQueue.main.async {
@ -217,6 +221,7 @@ extension SearchResultsViewController: UISearchBarDelegate {
} }
extension SearchResultsViewController: StatusTableViewCellDelegate { extension SearchResultsViewController: StatusTableViewCellDelegate {
var apiController: MastodonController { mastodonController }
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
tableView.beginUpdates() tableView.beginUpdates()
tableView.endUpdates() tableView.endUpdates()

View File

@ -14,6 +14,8 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
private let statusCell = "statusCell" private let statusCell = "statusCell"
private let accountCell = "accountCell" private let accountCell = "accountCell"
let mastodonController: MastodonController
let actionType: ActionType let actionType: ActionType
let statusID: String let statusID: String
var statusState: StatusState var statusState: StatusState
@ -32,8 +34,11 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
- Parameter actionType The action that this VC is for. - Parameter actionType The action that this VC is for.
- Parameter statusID The ID of the status to show. - Parameter statusID The ID of the status to show.
- Parameter accountIDs The accounts that will be shown. If `nil` is passed, a request will be performed to load the accounts. - Parameter accountIDs The accounts that will be shown. If `nil` is passed, a request will be performed to load the accounts.
- Parameter mastodonController The `MastodonController` instance this view controller uses.
*/ */
init(actionType: ActionType, statusID: String, statusState: StatusState, accountIDs: [String]?) { init(actionType: ActionType, statusID: String, statusState: StatusState, accountIDs: [String]?, mastodonController: MastodonController) {
self.mastodonController = mastodonController
self.actionType = actionType self.actionType = actionType
self.statusID = statusID self.statusID = statusID
self.statusState = statusState self.statusState = statusState
@ -75,7 +80,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
tableView.tableFooterView = UIActivityIndicatorView(style: .large) tableView.tableFooterView = UIActivityIndicatorView(style: .large)
let request = actionType == .favorite ? Status.getFavourites(status) : Status.getReblogs(status) let request = actionType == .favorite ? Status.getFavourites(status) : Status.getReblogs(status)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(accounts, _) = response else { fatalError() } guard case let .success(accounts, _) = response else { fatalError() }
MastodonCache.addAll(accounts: accounts) MastodonCache.addAll(accounts: accounts)
DispatchQueue.main.async { DispatchQueue.main.async {
@ -137,6 +142,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
} }
extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate { extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate {
var apiController: MastodonController { mastodonController }
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights // causes the table view to recalculate the cell heights
tableView.beginUpdates() tableView.beginUpdates()

View File

@ -22,10 +22,10 @@ class HashtagTimelineViewController: TimelineTableViewController {
} }
} }
init(for hashtag: Hashtag) { init(for hashtag: Hashtag, mastodonController: MastodonController) {
self.hashtag = hashtag self.hashtag = hashtag
super.init(for: .tag(hashtag: hashtag.name)) super.init(for: .tag(hashtag: hashtag.name), mastodonController: mastodonController)
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {

View File

@ -31,7 +31,10 @@ class InstanceTimelineViewController: TimelineTableViewController {
init(for url: URL) { init(for url: URL) {
self.instanceURL = url self.instanceURL = url
super.init(for: .instance(instanceURL: url)) let mastodonController = MastodonController()
mastodonController.createClient(instanceURL: url)
super.init(for: .instance(instanceURL: url), mastodonController: mastodonController)
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {

View File

@ -12,6 +12,7 @@ import Pachyderm
class TimelineTableViewController: EnhancedTableViewController { class TimelineTableViewController: EnhancedTableViewController {
var timeline: Timeline! var timeline: Timeline!
let mastodonController: MastodonController
var timelineSegments: [[(id: String, state: StatusState)]] = [] { var timelineSegments: [[(id: String, state: StatusState)]] = [] {
didSet { didSet {
@ -24,8 +25,9 @@ class TimelineTableViewController: EnhancedTableViewController {
var newer: RequestRange? var newer: RequestRange?
var older: RequestRange? var older: RequestRange?
init(for timeline: Timeline) { init(for timeline: Timeline, mastodonController: MastodonController) {
self.timeline = timeline self.timeline = timeline
self.mastodonController = mastodonController
super.init(style: .plain) super.init(style: .plain)
@ -56,13 +58,13 @@ class TimelineTableViewController: EnhancedTableViewController {
tableView.prefetchDataSource = self tableView.prefetchDataSource = self
guard MastodonController.accessToken != nil else { return } guard mastodonController.accessToken != nil else { return }
loadInitialStatuses() loadInitialStatuses()
} }
func loadInitialStatuses() { func loadInitialStatuses() {
let request = Client.getStatuses(timeline: timeline) let request = Client.getStatuses(timeline: timeline)
MastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(statuses, pagination) = response else { fatalError() } guard case let .success(statuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: statuses) MastodonCache.addAll(statuses: statuses)
self.timelineSegments.insert(statuses.map { ($0.id, .unknown) }, at: 0) self.timelineSegments.insert(statuses.map { ($0.id, .unknown) }, at: 0)
@ -100,7 +102,7 @@ class TimelineTableViewController: EnhancedTableViewController {
guard let older = older else { return } guard let older = older else { return }
let request = Client.getStatuses(timeline: timeline, range: older) let request = Client.getStatuses(timeline: timeline, range: older)
MastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(newStatuses, pagination) = response else { fatalError() } guard case let .success(newStatuses, pagination) = response else { fatalError() }
self.older = pagination?.older self.older = pagination?.older
MastodonCache.addAll(statuses: newStatuses) MastodonCache.addAll(statuses: newStatuses)
@ -125,7 +127,7 @@ class TimelineTableViewController: EnhancedTableViewController {
guard let newer = newer else { return } guard let newer = newer else { return }
let request = Client.getStatuses(timeline: timeline, range: newer) let request = Client.getStatuses(timeline: timeline, range: newer)
MastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(newStatuses, pagination) = response else { fatalError() } guard case let .success(newStatuses, pagination) = response else { fatalError() }
self.newer = pagination?.newer self.newer = pagination?.newer
MastodonCache.addAll(statuses: newStatuses) MastodonCache.addAll(statuses: newStatuses)
@ -146,6 +148,8 @@ class TimelineTableViewController: EnhancedTableViewController {
} }
extension TimelineTableViewController: StatusTableViewCellDelegate { extension TimelineTableViewController: StatusTableViewCellDelegate {
var apiController: MastodonController { mastodonController }
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights // causes the table view to recalculate the cell heights
tableView.beginUpdates() tableView.beginUpdates()

View File

@ -14,14 +14,18 @@ class TimelinesPageViewController: SegmentedPageViewController {
private let federatedTitle = NSLocalizedString("Federated", comment: "federated timeline tab title") private let federatedTitle = NSLocalizedString("Federated", comment: "federated timeline tab title")
private let localTitle = NSLocalizedString("Local", comment: "local timeline tab title") private let localTitle = NSLocalizedString("Local", comment: "local timeline tab title")
init() { let mastodonController: MastodonController
let home = TimelineTableViewController(for: .home)
init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
let home = TimelineTableViewController(for: .home, mastodonController: mastodonController)
home.title = homeTitle home.title = homeTitle
let federated = TimelineTableViewController(for: .public(local: false)) let federated = TimelineTableViewController(for: .public(local: false), mastodonController: mastodonController)
federated.title = federatedTitle federated.title = federatedTitle
let local = TimelineTableViewController(for: .public(local: true)) let local = TimelineTableViewController(for: .public(local: true), mastodonController: mastodonController)
local.title = localTitle local.title = localTitle
super.init(titles: [ super.init(titles: [

View File

@ -15,6 +15,8 @@ class UserActivityManager {
private static let encoder = PropertyListEncoder() private static let encoder = PropertyListEncoder()
private static let decoder = PropertyListDecoder() private static let decoder = PropertyListDecoder()
private static var mastodonController: MastodonController { .shared }
private static func getMainTabBarController() -> MainTabBarViewController { private static func getMainTabBarController() -> MainTabBarViewController {
return (UIApplication.shared.delegate! as! AppDelegate).window!.rootViewController as! MainTabBarViewController return (UIApplication.shared.delegate! as! AppDelegate).window!.rootViewController as! MainTabBarViewController
} }
@ -42,7 +44,8 @@ class UserActivityManager {
static func handleNewPost(activity: NSUserActivity) { static func handleNewPost(activity: NSUserActivity) {
// TODO: check not currently showing compose screen // TODO: check not currently showing compose screen
let mentioning = activity.userInfo?["mentioning"] as? String let mentioning = activity.userInfo?["mentioning"] as? String
present(UINavigationController(rootViewController: ComposeViewController(mentioningAcct: mentioning))) let composeVC = ComposeViewController(mentioningAcct: mentioning, mastodonController: mastodonController)
present(UINavigationController(rootViewController: composeVC))
} }
// MARK: - Check Notifications // MARK: - Check Notifications
@ -144,7 +147,8 @@ class UserActivityManager {
rootController.segmentedControl.selectedSegmentIndex = index rootController.segmentedControl.selectedSegmentIndex = index
rootController.selectPage(at: index, animated: false) rootController.selectPage(at: index, animated: false)
default: default:
navigationController.pushViewController(TimelineTableViewController(for: timeline), animated: false) let timeline = TimelineTableViewController(for: timeline, mastodonController: mastodonController)
navigationController.pushViewController(timeline, animated: false)
} }
} }
@ -182,7 +186,7 @@ class UserActivityManager {
tabBarController.select(tab: .explore) tabBarController.select(tab: .explore)
if let navigationController = tabBarController.getTabController(tab: .explore) as? UINavigationController { if let navigationController = tabBarController.getTabController(tab: .explore) as? UINavigationController {
navigationController.popToRootViewController(animated: false) navigationController.popToRootViewController(animated: false)
navigationController.pushViewController(BookmarksTableViewController(), animated: false) navigationController.pushViewController(BookmarksTableViewController(mastodonController: mastodonController), animated: false)
} }
} }

View File

@ -12,6 +12,8 @@ import Pachyderm
protocol TuskerNavigationDelegate { protocol TuskerNavigationDelegate {
var apiController: MastodonController { get }
func show(_ vc: UIViewController) func show(_ vc: UIViewController)
func selected(account accountID: String) func selected(account accountID: String)
@ -68,15 +70,15 @@ extension TuskerNavigationDelegate where Self: UIViewController {
return return
} }
show(ProfileTableViewController(accountID: accountID), sender: self) show(ProfileTableViewController(accountID: accountID, mastodonController: apiController), sender: self)
} }
func selected(mention: Mention) { func selected(mention: Mention) {
show(ProfileTableViewController(accountID: mention.id), sender: self) show(ProfileTableViewController(accountID: mention.id, mastodonController: apiController), sender: self)
} }
func selected(tag: Hashtag) { func selected(tag: Hashtag) {
show(HashtagTimelineViewController(for: tag), sender: self) show(HashtagTimelineViewController(for: tag, mastodonController: apiController), sender: self)
} }
func selected(url: URL) { func selected(url: URL) {
@ -113,7 +115,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
return return
} }
show(ConversationTableViewController(for: statusID, state: state), sender: self) show(ConversationTableViewController(for: statusID, state: state, mastodonController: apiController), sender: self)
} }
// protocols can't have parameter defaults, so this stub is necessary to fulfill the protocol req // protocols can't have parameter defaults, so this stub is necessary to fulfill the protocol req
@ -122,14 +124,14 @@ extension TuskerNavigationDelegate where Self: UIViewController {
} }
func compose(mentioning: String? = nil) { func compose(mentioning: String? = nil) {
let compose = ComposeViewController( mentioningAcct: mentioning) let compose = ComposeViewController(mentioningAcct: mentioning, mastodonController: apiController)
let vc = UINavigationController(rootViewController: compose) let vc = UINavigationController(rootViewController: compose)
vc.presentationController?.delegate = compose vc.presentationController?.delegate = compose
present(vc, animated: true) present(vc, animated: true)
} }
func reply(to statusID: String) { func reply(to statusID: String) {
let compose = ComposeViewController(inReplyTo: statusID) let compose = ComposeViewController(inReplyTo: statusID, mastodonController: apiController)
let vc = UINavigationController(rootViewController: compose) let vc = UINavigationController(rootViewController: compose)
vc.presentationController?.delegate = compose vc.presentationController?.delegate = compose
present(vc, animated: true) present(vc, animated: true)
@ -200,7 +202,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0) customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
} }
if status.account == MastodonController.account, if status.account == apiController.account,
let pinned = status.pinned { let pinned = status.pinned {
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1) customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
} }
@ -228,13 +230,13 @@ extension TuskerNavigationDelegate where Self: UIViewController {
} }
func showFollowedByList(accountIDs: [String]) { func showFollowedByList(accountIDs: [String]) {
let vc = AccountListTableViewController(accountIDs: accountIDs) let vc = AccountListTableViewController(accountIDs: accountIDs, mastodonController: apiController)
vc.title = NSLocalizedString("Followed By", comment: "followed by accounts list title") vc.title = NSLocalizedString("Followed By", comment: "followed by accounts list title")
show(vc, sender: self) show(vc, sender: self)
} }
func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, statusState state: StatusState, accountIDs: [String]?) -> StatusActionAccountListTableViewController { func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, statusState state: StatusState, accountIDs: [String]?) -> StatusActionAccountListTableViewController {
return StatusActionAccountListTableViewController(actionType: action, statusID: statusID, statusState: state, accountIDs: accountIDs) return StatusActionAccountListTableViewController(actionType: action, statusID: statusID, statusState: state, accountIDs: accountIDs, mastodonController: apiController)
} }
} }

View File

@ -11,6 +11,7 @@ import UIKit
class AccountTableViewCell: UITableViewCell { class AccountTableViewCell: UITableViewCell {
var delegate: TuskerNavigationDelegate? var delegate: TuskerNavigationDelegate?
var mastodonController: MastodonController? { delegate?.apiController }
@IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var displayNameLabel: UILabel! @IBOutlet weak var displayNameLabel: UILabel!
@ -68,6 +69,11 @@ extension AccountTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate } var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) }) guard let mastodonController = mastodonController else { return nil }
return (content: {
ProfileTableViewController(accountID: self.accountID, mastodonController: mastodonController)
}, actions: {
self.actionsForProfile(accountID: self.accountID)
})
} }
} }

View File

@ -178,13 +178,15 @@ class ContentLabel: LinkLabel {
func getViewController(forLink url: URL, inRange range: NSRange) -> UIViewController { func getViewController(forLink url: URL, inRange range: NSRange) -> UIViewController {
let text = (self.text! as NSString).substring(with: range) let text = (self.text! as NSString).substring(with: range)
if let mention = getMention(for: url, text: text) { if let navigationDelegate = navigationDelegate {
return ProfileTableViewController(accountID: mention.id) if let mention = getMention(for: url, text: text) {
} else if let tag = getHashtag(for: url, text: text) { return ProfileTableViewController(accountID: mention.id, mastodonController: navigationDelegate.apiController)
return HashtagTimelineViewController(for: tag) } else if let tag = getHashtag(for: url, text: text) {
} else { return HashtagTimelineViewController(for: tag, mastodonController: navigationDelegate.apiController)
return SFSafariViewController(url: url) }
} }
return SFSafariViewController(url: url)
} }
func getViewController(forLinkAt point: CGPoint) -> UIViewController? { func getViewController(forLinkAt point: CGPoint) -> UIViewController? {

View File

@ -12,6 +12,7 @@ import Pachyderm
class FollowNotificationGroupTableViewCell: UITableViewCell { class FollowNotificationGroupTableViewCell: UITableViewCell {
var delegate: TuskerNavigationDelegate? var delegate: TuskerNavigationDelegate?
var mastodonController: MastodonController? { delegate?.apiController }
@IBOutlet weak var avatarStackView: UIStackView! @IBOutlet weak var avatarStackView: UIStackView!
@IBOutlet weak var timestampLabel: UILabel! @IBOutlet weak var timestampLabel: UILabel!
@ -133,12 +134,13 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate } var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
guard let mastodonController = mastodonController else { return nil }
return (content: { return (content: {
let accountIDs = self.group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account.id } let accountIDs = self.group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account.id }
if accountIDs.count == 1 { if accountIDs.count == 1 {
return ProfileTableViewController(accountID: accountIDs.first!) return ProfileTableViewController(accountID: accountIDs.first!, mastodonController: mastodonController)
} else { } else {
return AccountListTableViewController(accountIDs: accountIDs) return AccountListTableViewController(accountIDs: accountIDs, mastodonController: mastodonController)
} }
}, actions: { }, actions: {
return [] return []

View File

@ -12,6 +12,7 @@ import Pachyderm
class FollowRequestNotificationTableViewCell: UITableViewCell { class FollowRequestNotificationTableViewCell: UITableViewCell {
var delegate: TuskerNavigationDelegate? var delegate: TuskerNavigationDelegate?
var mastodonController: MastodonController? { delegate?.apiController }
@IBOutlet weak var stackView: UIStackView! @IBOutlet weak var stackView: UIStackView!
@IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var avatarImageView: UIImageView!
@ -89,7 +90,7 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
@IBAction func rejectButtonPressed() { @IBAction func rejectButtonPressed() {
let request = Account.rejectFollowRequest(account) let request = Account.rejectFollowRequest(account)
MastodonController.run(request) { (response) in mastodonController!.run(request) { (response) in
guard case let .success(relationship, _) = response else { fatalError() } guard case let .success(relationship, _) = response else { fatalError() }
MastodonCache.add(relationship: relationship) MastodonCache.add(relationship: relationship)
DispatchQueue.main.async { DispatchQueue.main.async {
@ -106,7 +107,7 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
@IBAction func acceptButtonPressed() { @IBAction func acceptButtonPressed() {
let request = Account.authorizeFollowRequest(account) let request = Account.authorizeFollowRequest(account)
MastodonController.run(request) { (response) in mastodonController!.run(request) { (response) in
guard case let .success(relationship, _) = response else { fatalError() } guard case let .success(relationship, _) = response else { fatalError() }
MastodonCache.add(relationship: relationship) MastodonCache.add(relationship: relationship)
DispatchQueue.main.async { DispatchQueue.main.async {
@ -133,8 +134,9 @@ extension FollowRequestNotificationTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate } var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
guard let mastodonController = mastodonController else { return nil }
return (content: { return (content: {
return ProfileTableViewController(accountID: self.account.id) return ProfileTableViewController(accountID: self.account.id, mastodonController: mastodonController)
}, actions: { }, actions: {
return [] return []
}) })

View File

@ -16,6 +16,7 @@ protocol ProfileHeaderTableViewCellDelegate: TuskerNavigationDelegate {
class ProfileHeaderTableViewCell: UITableViewCell { class ProfileHeaderTableViewCell: UITableViewCell {
var delegate: ProfileHeaderTableViewCellDelegate? var delegate: ProfileHeaderTableViewCellDelegate?
var mastodonController: MastodonController? { delegate?.apiController }
@IBOutlet weak var headerImageView: UIImageView! @IBOutlet weak var headerImageView: UIImageView!
@IBOutlet weak var avatarContainerView: UIView! @IBOutlet weak var avatarContainerView: UIView!
@ -82,7 +83,7 @@ class ProfileHeaderTableViewCell: UITableViewCell {
noteLabel.setTextFromHtml(account.note) noteLabel.setTextFromHtml(account.note)
noteLabel.setEmojis(account.emojis) noteLabel.setEmojis(account.emojis)
if accountID != MastodonController.account.id { if accountID != mastodonController!.account.id {
// don't show relationship label for the user's own account // don't show relationship label for the user's own account
if let relationship = MastodonCache.relationship(for: accountID) { if let relationship = MastodonCache.relationship(for: accountID) {
followsYouLabel.isHidden = !relationship.followedBy followsYouLabel.isHidden = !relationship.followedBy

View File

@ -15,11 +15,13 @@ protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
} }
class BaseStatusTableViewCell: UITableViewCell { class BaseStatusTableViewCell: UITableViewCell {
var delegate: StatusTableViewCellDelegate? { var delegate: StatusTableViewCellDelegate? {
didSet { didSet {
contentLabel.navigationDelegate = delegate contentLabel.navigationDelegate = delegate
} }
} }
var mastodonController: MastodonController? { delegate?.apiController }
@IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var displayNameLabel: UILabel! @IBOutlet weak var displayNameLabel: UILabel!
@ -247,7 +249,7 @@ class BaseStatusTableViewCell: UITableViewCell {
let realStatus: Status = status.reblog ?? status let realStatus: Status = status.reblog ?? status
let request = (favorited ? Status.favourite : Status.unfavourite)(realStatus) let request = (favorited ? Status.favourite : Status.unfavourite)(realStatus)
MastodonController.run(request) { response in mastodonController!.run(request) { response in
DispatchQueue.main.async { DispatchQueue.main.async {
if case let .success(newStatus, _) = response { if case let .success(newStatus, _) = response {
self.favorited = newStatus.favourited ?? false self.favorited = newStatus.favourited ?? false
@ -272,7 +274,7 @@ class BaseStatusTableViewCell: UITableViewCell {
let realStatus: Status = status.reblog ?? status let realStatus: Status = status.reblog ?? status
let request = (reblogged ? Status.reblog : Status.unreblog)(realStatus) let request = (reblogged ? Status.reblog : Status.unreblog)(realStatus)
MastodonController.run(request) { response in mastodonController!.run(request) { response in
DispatchQueue.main.async { DispatchQueue.main.async {
if case let .success(newStatus, _) = response { if case let .success(newStatus, _) = response {
self.reblogged = newStatus.reblogged ?? false self.reblogged = newStatus.reblogged ?? false
@ -313,8 +315,13 @@ extension BaseStatusTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate } var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
guard let mastodonController = mastodonController else { return nil }
if avatarImageView.frame.contains(location) { if avatarImageView.frame.contains(location) {
return (content: { ProfileTableViewController(accountID: self.accountID)}, actions: { self.actionsForProfile(accountID: self.accountID) }) return (content: {
ProfileTableViewController(accountID: self.accountID, mastodonController: mastodonController)
}, actions: {
self.actionsForProfile(accountID: self.accountID)
})
} else if attachmentsView.frame.contains(location) { } else if attachmentsView.frame.contains(location) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self) let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.attachmentViews.allObjects.first(where: { $0.frame.contains(attachmentsViewLocation) }), if let attachmentView = attachmentsView.attachmentViews.allObjects.first(where: { $0.frame.contains(attachmentsViewLocation) }),

View File

@ -125,8 +125,9 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
} }
override func getStatusCellPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> BaseStatusTableViewCell.PreviewProviders? { override func getStatusCellPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> BaseStatusTableViewCell.PreviewProviders? {
guard let mastodonController = mastodonController else { return nil }
return ( return (
content: { ConversationTableViewController(for: self.statusID, state: self.statusState.copy()) }, content: { ConversationTableViewController(for: self.statusID, state: self.statusState.copy(), mastodonController: mastodonController) },
actions: { self.actionsForStatus(statusID: self.statusID) } actions: { self.actionsForStatus(statusID: self.statusID) }
) )
} }
@ -142,6 +143,7 @@ extension TimelineStatusTableViewCell: SelectableTableViewCell {
extension TimelineStatusTableViewCell: TableViewSwipeActionProvider { extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? { func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
guard let mastodonController = mastodonController else { return nil }
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
let favoriteTitle: String let favoriteTitle: String
@ -158,7 +160,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
favoriteColor = UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1) favoriteColor = UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
} }
let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { (action, view, completion) in let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { (action, view, completion) in
MastodonController.run(favoriteRequest, completion: { response in mastodonController.run(favoriteRequest, completion: { response in
DispatchQueue.main.async { DispatchQueue.main.async {
guard case let .success(status, _) = response else { guard case let .success(status, _) = response else {
completion(false) completion(false)
@ -185,7 +187,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
reblogColor = tintColor reblogColor = tintColor
} }
let reblog = UIContextualAction(style: .normal, title: reblogTitle) { (action, view, completion) in let reblog = UIContextualAction(style: .normal, title: reblogTitle) { (action, view, completion) in
MastodonController.run(reblogRequest, completion: { response in mastodonController.run(reblogRequest, completion: { response in
DispatchQueue.main.async { DispatchQueue.main.async {
guard case let .success(status, _) = response else { guard case let .success(status, _) = response else {
completion(false) completion(false)

View File

@ -13,6 +13,8 @@ import SwiftSoup
struct XCBActions { struct XCBActions {
// MARK: - Utils // MARK: - Utils
private static var mastodonController: MastodonController { .shared }
private static func getMainTabBarController() -> MainTabBarViewController { private static func getMainTabBarController() -> MainTabBarViewController {
return (UIApplication.shared.delegate as! AppDelegate).window!.rootViewController as! MainTabBarViewController return (UIApplication.shared.delegate as! AppDelegate).window!.rootViewController as! MainTabBarViewController
} }
@ -42,7 +44,7 @@ struct XCBActions {
} }
} else if let searchQuery = request.arguments["statusURL"] { } else if let searchQuery = request.arguments["statusURL"] {
let request = Client.search(query: searchQuery) let request = Client.search(query: searchQuery)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(results, _) = response, if case let .success(results, _) = response,
let status = results.statuses.first { let status = results.statuses.first {
MastodonCache.add(status: status) MastodonCache.add(status: status)
@ -73,7 +75,7 @@ struct XCBActions {
} }
} else if let searchQuery = request.arguments["accountURL"] { } else if let searchQuery = request.arguments["accountURL"] {
let request = Client.search(query: searchQuery) let request = Client.search(query: searchQuery)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(results, _) = response { if case let .success(results, _) = response {
if let account = results.accounts.first { if let account = results.accounts.first {
MastodonCache.add(account: account) MastodonCache.add(account: account)
@ -91,7 +93,7 @@ struct XCBActions {
} }
} else if let acct = request.arguments["acct"] { } else if let acct = request.arguments["acct"] {
let request = Client.searchForAccount(query: acct) let request = Client.searchForAccount(query: acct)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(accounts, _) = response { if case let .success(accounts, _) = response {
if let account = accounts.first { if let account = accounts.first {
MastodonCache.add(account: account) MastodonCache.add(account: account)
@ -118,7 +120,7 @@ struct XCBActions {
static func showStatus(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) { static func showStatus(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
getStatus(from: request, session: session) { (status) in getStatus(from: request, session: session) { (status) in
DispatchQueue.main.async { DispatchQueue.main.async {
let vc = ConversationTableViewController(for: status.id) let vc = ConversationTableViewController(for: status.id, mastodonController: mastodonController)
show(vc) show(vc)
} }
} }
@ -132,14 +134,14 @@ struct XCBActions {
var status = "" var status = ""
if let mentioning = mentioning { status += mentioning } if let mentioning = mentioning { status += mentioning }
if let text = text { status += text } if let text = text { status += text }
guard CharacterCounter.count(text: status) <= MastodonController.instance.maxStatusCharacters ?? 500 else { guard CharacterCounter.count(text: status) <= mastodonController.instance.maxStatusCharacters ?? 500 else {
session.complete(with: .error, additionalData: [ session.complete(with: .error, additionalData: [
"error": "Too many characters. Instance maximum is \(MastodonController.instance.maxStatusCharacters ?? 500)" "error": "Too many characters. Instance maximum is \(mastodonController.instance.maxStatusCharacters ?? 500)"
]) ])
return return
} }
let request = Client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility) let request = Client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility)
MastodonController.run(request) { response in mastodonController.run(request) { response in
if case let .success(status, _) = response { if case let .success(status, _) = response {
session.complete(with: .success, additionalData: [ session.complete(with: .success, additionalData: [
"statusURL": status.url?.absoluteString, "statusURL": status.url?.absoluteString,
@ -152,7 +154,7 @@ struct XCBActions {
} }
} }
} else { } else {
let compose = ComposeViewController(mentioningAcct: mentioning, text: text) let compose = ComposeViewController(mentioningAcct: mentioning, text: text, mastodonController: mastodonController)
compose.xcbSession = session compose.xcbSession = session
let vc = UINavigationController(rootViewController: compose) let vc = UINavigationController(rootViewController: compose)
present(vc) present(vc)
@ -199,7 +201,7 @@ struct XCBActions {
static func statusAction(request: @escaping (Status) -> Request<Status>, alertTitle: String, _ url: XCBRequest, _ session: XCBSession, _ silent: Bool?) { static func statusAction(request: @escaping (Status) -> Request<Status>, alertTitle: String, _ url: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
func performAction(status: Status, completion: ((Status) -> Void)?) { func performAction(status: Status, completion: ((Status) -> Void)?) {
MastodonController.run(request(status)) { (response) in mastodonController.run(request(status)) { (response) in
if case let .success(status, _) = response { if case let .success(status, _) = response {
MastodonCache.add(status: status) MastodonCache.add(status: status)
completion?(status) completion?(status)
@ -219,7 +221,7 @@ struct XCBActions {
if silent ?? false { if silent ?? false {
performAction(status: status, completion: nil) performAction(status: status, completion: nil)
} else { } else {
let vc = ConversationTableViewController(for: status.id) let vc = ConversationTableViewController(for: status.id, mastodonController: mastodonController)
DispatchQueue.main.async { DispatchQueue.main.async {
show(vc) show(vc)
} }
@ -247,7 +249,7 @@ struct XCBActions {
static func showAccount(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) { static func showAccount(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
getAccount(from: request, session: session) { (account) in getAccount(from: request, session: session) { (account) in
DispatchQueue.main.async { DispatchQueue.main.async {
let vc = ProfileTableViewController(accountID: account.id) let vc = ProfileTableViewController(accountID: account.id, mastodonController: mastodonController)
show(vc) show(vc)
} }
} }
@ -269,7 +271,7 @@ struct XCBActions {
} }
static func getCurrentUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) { static func getCurrentUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
let account = MastodonController.account! let account = mastodonController.account!
session.complete(with: .success, additionalData: [ session.complete(with: .success, additionalData: [
"username": account.acct, "username": account.acct,
"displayName": account.displayName, "displayName": account.displayName,
@ -285,7 +287,7 @@ struct XCBActions {
static func followUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) { static func followUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
func performAction(_ account: Account) { func performAction(_ account: Account) {
let request = Account.follow(account.id) let request = Account.follow(account.id)
MastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
if case let .success(relationship, _) = response { if case let .success(relationship, _) = response {
MastodonCache.add(relationship: relationship) MastodonCache.add(relationship: relationship)
session.complete(with: .success, additionalData: [ session.complete(with: .success, additionalData: [
@ -303,7 +305,7 @@ struct XCBActions {
if silent ?? false { if silent ?? false {
performAction(account) performAction(account)
} else { } else {
let vc = ProfileTableViewController(accountID: account.id) let vc = ProfileTableViewController(accountID: account.id, mastodonController: mastodonController)
DispatchQueue.main.async { DispatchQueue.main.async {
show(vc) show(vc)
} }