Compare commits
6 Commits
863867c522
...
3aa5aa1bc0
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 3aa5aa1bc0 | |
Shadowfacts | ee252c02e9 | |
Shadowfacts | 2f630f2f8f | |
Shadowfacts | 8eb6f6f573 | |
Shadowfacts | 32e89f2c16 | |
Shadowfacts | c45dd99088 |
|
@ -21,6 +21,7 @@ class LocalData: ObservableObject {
|
||||||
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") {
|
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") {
|
||||||
accounts = [
|
accounts = [
|
||||||
UserAccountInfo(
|
UserAccountInfo(
|
||||||
|
id: UUID().uuidString,
|
||||||
instanceURL: URL(string: "http://localhost:8080")!,
|
instanceURL: URL(string: "http://localhost:8080")!,
|
||||||
clientID: "client_id",
|
clientID: "client_id",
|
||||||
clientSecret: "client_secret",
|
clientSecret: "client_secret",
|
||||||
|
@ -38,15 +39,16 @@ class LocalData: ObservableObject {
|
||||||
get {
|
get {
|
||||||
if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
|
if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
|
||||||
return array.compactMap { (info) in
|
return array.compactMap { (info) in
|
||||||
guard let instanceURL = info["instanceURL"],
|
guard let id = info["id"],
|
||||||
|
let instanceURL = info["instanceURL"],
|
||||||
let url = URL(string: instanceURL),
|
let url = URL(string: instanceURL),
|
||||||
let id = info["clientID"],
|
let clientId = info["clientID"],
|
||||||
let secret = info["clientSecret"],
|
let secret = info["clientSecret"],
|
||||||
let username = info["username"],
|
let username = info["username"],
|
||||||
let accessToken = info["accessToken"] else {
|
let accessToken = info["accessToken"] else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return UserAccountInfo(instanceURL: url, clientID: id, clientSecret: secret, username: username, accessToken: accessToken)
|
return UserAccountInfo(id: id, instanceURL: url, clientID: clientId, clientSecret: secret, username: username, accessToken: accessToken)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return []
|
return []
|
||||||
|
@ -56,6 +58,7 @@ class LocalData: ObservableObject {
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
let array = newValue.map { (info) in
|
let array = newValue.map { (info) in
|
||||||
return [
|
return [
|
||||||
|
"id": info.id,
|
||||||
"instanceURL": info.instanceURL.absoluteString,
|
"instanceURL": info.instanceURL.absoluteString,
|
||||||
"clientID": info.clientID,
|
"clientID": info.clientID,
|
||||||
"clientSecret": info.clientSecret,
|
"clientSecret": info.clientSecret,
|
||||||
|
@ -68,7 +71,7 @@ class LocalData: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private let mostRecentAccountKey = "mostRecentAccount"
|
private let mostRecentAccountKey = "mostRecentAccount"
|
||||||
var mostRecentAccount: String? {
|
private var mostRecentAccount: String? {
|
||||||
get {
|
get {
|
||||||
return defaults.string(forKey: mostRecentAccountKey)
|
return defaults.string(forKey: mostRecentAccountKey)
|
||||||
}
|
}
|
||||||
|
@ -82,41 +85,60 @@ class LocalData: ObservableObject {
|
||||||
return !accounts.isEmpty
|
return !accounts.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
func addAccount(instanceURL url: URL, clientID id: String, clientSecret secret: String, username: String, accessToken: String) -> UserAccountInfo {
|
func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String, accessToken: String) -> UserAccountInfo {
|
||||||
var accounts = self.accounts
|
var accounts = self.accounts
|
||||||
if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) {
|
if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) {
|
||||||
accounts.remove(at: index)
|
accounts.remove(at: index)
|
||||||
}
|
}
|
||||||
let info = UserAccountInfo(instanceURL: url, clientID: id, clientSecret: secret, username: username, accessToken: accessToken)
|
let id = UUID().uuidString
|
||||||
|
let info = UserAccountInfo(id: id, instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken)
|
||||||
accounts.append(info)
|
accounts.append(info)
|
||||||
self.accounts = accounts
|
self.accounts = accounts
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAccount(_ info: UserAccountInfo) {
|
func removeAccount(_ info: UserAccountInfo) {
|
||||||
|
accounts.removeAll(where: { $0.id == info.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMostRecentAccount() -> UserAccountInfo? {
|
func getMostRecentAccount() -> UserAccountInfo? {
|
||||||
if let accessToken = mostRecentAccount {
|
guard onboardingComplete else { return nil }
|
||||||
return accounts.first { $0.accessToken == accessToken }
|
let mostRecent: UserAccountInfo?
|
||||||
|
if let id = mostRecentAccount {
|
||||||
|
mostRecent = accounts.first { $0.id == id }
|
||||||
} else {
|
} else {
|
||||||
return nil
|
mostRecent = nil
|
||||||
}
|
}
|
||||||
|
return mostRecent ?? accounts.first!
|
||||||
|
}
|
||||||
|
|
||||||
|
func setMostRecentAccount(_ account: UserAccountInfo?) {
|
||||||
|
mostRecentAccount = account?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LocalData {
|
extension LocalData {
|
||||||
struct UserAccountInfo: Equatable, Hashable {
|
struct UserAccountInfo: Equatable, Hashable {
|
||||||
|
let id: String
|
||||||
let instanceURL: URL
|
let instanceURL: URL
|
||||||
let clientID: String
|
let clientID: String
|
||||||
let clientSecret: String
|
let clientSecret: String
|
||||||
let username: String
|
let username: String
|
||||||
let accessToken: String
|
let accessToken: String
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: UserAccountInfo, rhs: UserAccountInfo) -> Bool {
|
||||||
|
return lhs.id == rhs.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
static let userLoggedOut = Notification.Name("userLoggedOut")
|
static let userLoggedOut = Notification.Name("Tusker.userLoggedOut")
|
||||||
|
static let addAccount = Notification.Name("Tusker.addAccount")
|
||||||
|
static let activateAccount = Notification.Name("Tusker.activateAccount")
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class MastodonCache {
|
||||||
let statusSubject = PassthroughSubject<Status, Never>()
|
let statusSubject = PassthroughSubject<Status, Never>()
|
||||||
let accountSubject = PassthroughSubject<Account, Never>()
|
let accountSubject = PassthroughSubject<Account, Never>()
|
||||||
|
|
||||||
let mastodonController: MastodonController
|
weak var mastodonController: MastodonController?
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
@ -43,6 +43,9 @@ class MastodonCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
func status(for id: String, completion: @escaping (Status?) -> Void) {
|
func status(for id: String, completion: @escaping (Status?) -> Void) {
|
||||||
|
guard let mastodonController = mastodonController else {
|
||||||
|
fatalError("The MastodonController for this cache has been deinitialized, so this cache should no longer exist. Are you storing a strong reference to it?")
|
||||||
|
}
|
||||||
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 {
|
||||||
|
@ -73,6 +76,9 @@ class MastodonCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
func account(for id: String, completion: @escaping (Account?) -> Void) {
|
func account(for id: String, completion: @escaping (Account?) -> Void) {
|
||||||
|
guard let mastodonController = mastodonController else {
|
||||||
|
fatalError("The MastodonController for this cache has been deinitialized, so this cache should no longer exist. Are you storing a strong reference to it?")
|
||||||
|
}
|
||||||
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 {
|
||||||
|
@ -102,6 +108,9 @@ class MastodonCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
func relationship(for id: String, completion: @escaping (Relationship?) -> Void) {
|
func relationship(for id: String, completion: @escaping (Relationship?) -> Void) {
|
||||||
|
guard let mastodonController = mastodonController else {
|
||||||
|
fatalError("The MastodonController for this cache has been deinitialized, so this cache should no longer exist. Are you storing a strong reference to it?")
|
||||||
|
}
|
||||||
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,
|
||||||
|
|
|
@ -34,7 +34,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
window!.makeKeyAndVisible()
|
window!.makeKeyAndVisible()
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(onUserLoggedOut), name: .userLoggedOut, object: nil)
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(themePrefChanged), name: .themePreferenceChanged, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(themePrefChanged), name: .themePreferenceChanged, object: nil)
|
||||||
themePrefChanged()
|
themePrefChanged()
|
||||||
|
|
||||||
|
@ -110,11 +109,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func activateAccount(_ account: LocalData.UserAccountInfo) {
|
func activateAccount(_ account: LocalData.UserAccountInfo) {
|
||||||
LocalData.shared.mostRecentAccount = account.accessToken
|
LocalData.shared.setMostRecentAccount(account)
|
||||||
window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account)
|
window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account)
|
||||||
showAppUI()
|
showAppUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logoutCurrent() {
|
||||||
|
LocalData.shared.removeAccount(LocalData.shared.getMostRecentAccount()!)
|
||||||
|
if LocalData.shared.onboardingComplete {
|
||||||
|
activateAccount(LocalData.shared.accounts.first!)
|
||||||
|
} else {
|
||||||
|
showOnboardingUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func showAppUI() {
|
func showAppUI() {
|
||||||
let mastodonController = window!.windowScene!.session.mastodonController!
|
let mastodonController = window!.windowScene!.session.mastodonController!
|
||||||
mastodonController.getOwnAccount()
|
mastodonController.getOwnAccount()
|
||||||
|
@ -130,10 +138,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
window!.rootViewController = onboarding
|
window!.rootViewController = onboarding
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func onUserLoggedOut() {
|
|
||||||
showOnboardingUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func themePrefChanged() {
|
@objc func themePrefChanged() {
|
||||||
window?.overrideUserInterfaceStyle = Preferences.shared.theme
|
window?.overrideUserInterfaceStyle = Preferences.shared.theme
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol DraftsTableViewControllerDelegate {
|
protocol DraftsTableViewControllerDelegate: class {
|
||||||
func draftSelectionCanceled()
|
func draftSelectionCanceled()
|
||||||
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void)
|
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void)
|
||||||
func draftSelected(_ draft: DraftsManager.Draft)
|
func draftSelected(_ draft: DraftsManager.Draft)
|
||||||
|
@ -17,7 +17,7 @@ protocol DraftsTableViewControllerDelegate {
|
||||||
|
|
||||||
class DraftsTableViewController: UITableViewController {
|
class DraftsTableViewController: UITableViewController {
|
||||||
|
|
||||||
var delegate: DraftsTableViewControllerDelegate?
|
weak var delegate: DraftsTableViewControllerDelegate?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(nibName: "DraftsTableViewController", bundle: nil)
|
super.init(nibName: "DraftsTableViewController", bundle: nil)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
|
|
||||||
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
|
|
||||||
let mastodonController: MastodonController
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
|
|
|
@ -14,7 +14,7 @@ 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")
|
||||||
|
|
||||||
let mastodonController: MastodonController
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
protocol InstanceSelectorTableViewControllerDelegate {
|
protocol InstanceSelectorTableViewControllerDelegate: class {
|
||||||
func didSelectInstance(url: URL)
|
func didSelectInstance(url: URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ fileprivate let instanceCell = "instanceCell"
|
||||||
|
|
||||||
class InstanceSelectorTableViewController: UITableViewController {
|
class InstanceSelectorTableViewController: UITableViewController {
|
||||||
|
|
||||||
var delegate: InstanceSelectorTableViewControllerDelegate?
|
weak var delegate: InstanceSelectorTableViewControllerDelegate?
|
||||||
|
|
||||||
var dataSource: DataSource!
|
var dataSource: DataSource!
|
||||||
var searchController: UISearchController!
|
var searchController: UISearchController!
|
||||||
|
@ -55,7 +55,7 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.estimatedRowHeight = 120
|
tableView.estimatedRowHeight = 120
|
||||||
|
|
||||||
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
|
dataSource = DataSource(tableView: tableView, cellProvider: { [weak self] (tableView, indexPath, item) -> UITableViewCell? in
|
||||||
switch item {
|
switch item {
|
||||||
case let .selected(instance):
|
case let .selected(instance):
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell
|
||||||
|
|
|
@ -69,9 +69,9 @@ extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate
|
||||||
|
|
||||||
mastodonController.authorize(authorizationCode: authCode) { (accessToken) in
|
mastodonController.authorize(authorizationCode: authCode) { (accessToken) in
|
||||||
mastodonController.getOwnAccount { (account) in
|
mastodonController.getOwnAccount { (account) in
|
||||||
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
|
||||||
|
|
||||||
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
|
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import UIKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class PreferencesNavigationController: UINavigationController {
|
class PreferencesNavigationController: UINavigationController {
|
||||||
|
|
||||||
|
private var isSwitchingAccounts = false
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
let view = PreferencesView()
|
let view = PreferencesView()
|
||||||
|
@ -27,13 +29,16 @@ class PreferencesNavigationController: UINavigationController {
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(showAddAccount), name: .addAccount, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(showAddAccount), name: .addAccount, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(activateAccount(_:)), name: .activateAccount, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(activateAccount(_:)), name: .activateAccount, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(userLoggedOut), name: .userLoggedOut, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
// workaround for onDisappear not being called when a modally presented UIHostingController is dismissed
|
if !isSwitchingAccounts {
|
||||||
NotificationCenter.default.post(name: .preferencesChanged, object: nil)
|
// workaround for onDisappear not being called when a modally presented UIHostingController is dismissed
|
||||||
|
NotificationCenter.default.post(name: .preferencesChanged, object: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func donePressed() {
|
@objc func donePressed() {
|
||||||
|
@ -48,26 +53,37 @@ class PreferencesNavigationController: UINavigationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func cancelAddAccount() {
|
@objc func cancelAddAccount() {
|
||||||
dismiss(animated: true)
|
dismiss(animated: true) // dismisses instance selector
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func activateAccount(_ notification: Notification) {
|
@objc func activateAccount(_ notification: Notification) {
|
||||||
let account = notification.userInfo!["account"] as! LocalData.UserAccountInfo
|
let account = notification.userInfo!["account"] as! LocalData.UserAccountInfo
|
||||||
let sceneDelegate = self.view.window!.windowScene!.delegate as! SceneDelegate
|
let sceneDelegate = self.view.window!.windowScene!.delegate as! SceneDelegate
|
||||||
dismiss(animated: true) {
|
isSwitchingAccounts = true
|
||||||
|
dismiss(animated: true) { // dismiss preferences
|
||||||
sceneDelegate.activateAccount(account)
|
sceneDelegate.activateAccount(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func userLoggedOut() {
|
||||||
|
let sceneDelegate = self.view.window!.windowScene!.delegate as! SceneDelegate
|
||||||
|
isSwitchingAccounts = true
|
||||||
|
dismiss(animated: true) { // dismiss preferences
|
||||||
|
sceneDelegate.logoutCurrent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
|
||||||
static let addAccount = Notification.Name("Tusker.addAccount")
|
|
||||||
static let activateAccount = Notification.Name("Tusker.activateAccount")
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PreferencesNavigationController: OnboardingViewControllerDelegate {
|
extension PreferencesNavigationController: OnboardingViewControllerDelegate {
|
||||||
func didFinishOnboarding(account: LocalData.UserAccountInfo) {
|
func didFinishOnboarding(account: LocalData.UserAccountInfo) {
|
||||||
LocalData.shared.mostRecentAccount = account.accessToken
|
DispatchQueue.main.async {
|
||||||
|
let sceneDelegate = self.view.window!.windowScene!.delegate as! SceneDelegate
|
||||||
|
self.dismiss(animated: true) { // dismiss instance selector
|
||||||
|
self.dismiss(animated: true) { // dismiss preferences
|
||||||
|
sceneDelegate.activateAccount(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||||
struct PreferencesView: View {
|
struct PreferencesView: View {
|
||||||
@ObservedObject var localData = LocalData.shared
|
@ObservedObject var localData = LocalData.shared
|
||||||
@State private var showingLogoutConfirmation = false
|
@State private var showingLogoutConfirmation = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
// workaround: the navigation view is provided by MyProfileTableViewController so that it can inject the Done button
|
// workaround: the navigation view is provided by MyProfileTableViewController so that it can inject the Done button
|
||||||
// NavigationView {
|
// NavigationView {
|
||||||
|
@ -24,7 +24,7 @@ struct PreferencesView: View {
|
||||||
Text(account.username)
|
Text(account.username)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
Spacer()
|
Spacer()
|
||||||
if account.accessToken == self.localData.mostRecentAccount {
|
if account == self.localData.getMostRecentAccount() {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
@ -37,7 +37,7 @@ struct PreferencesView: View {
|
||||||
}) {
|
}) {
|
||||||
Text("Add Account...")
|
Text("Add Account...")
|
||||||
}
|
}
|
||||||
if localData.mostRecentAccount != nil {
|
if localData.getMostRecentAccount() != nil {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
self.showingLogoutConfirmation = true
|
self.showingLogoutConfirmation = true
|
||||||
}) {
|
}) {
|
||||||
|
@ -73,8 +73,6 @@ struct PreferencesView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
func logoutPressed() {
|
func logoutPressed() {
|
||||||
// LocalData.shared.removeAccount(currentAccount)
|
|
||||||
localData.removeAccount(localData.getMostRecentAccount()!)
|
|
||||||
NotificationCenter.default.post(name: .userLoggedOut, object: nil)
|
NotificationCenter.default.post(name: .userLoggedOut, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import SafariServices
|
||||||
|
|
||||||
class ProfileTableViewController: EnhancedTableViewController {
|
class ProfileTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
let mastodonController: MastodonController
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
var accountID: String! {
|
var accountID: String! {
|
||||||
didSet {
|
didSet {
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol InstanceTimelineViewControllerDelegate {
|
protocol InstanceTimelineViewControllerDelegate: class {
|
||||||
func didSaveInstance(url: URL)
|
func didSaveInstance(url: URL)
|
||||||
func didUnsaveInstance(url: URL)
|
func didUnsaveInstance(url: URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstanceTimelineViewController: TimelineTableViewController {
|
class InstanceTimelineViewController: TimelineTableViewController {
|
||||||
|
|
||||||
var delegate: InstanceTimelineViewControllerDelegate?
|
weak var delegate: InstanceTimelineViewControllerDelegate?
|
||||||
|
|
||||||
let instanceURL: URL
|
let instanceURL: URL
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import Pachyderm
|
||||||
class TimelineTableViewController: EnhancedTableViewController {
|
class TimelineTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
var timeline: Timeline!
|
var timeline: Timeline!
|
||||||
let mastodonController: MastodonController
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
var timelineSegments: [[(id: String, state: StatusState)]] = [] {
|
var timelineSegments: [[(id: String, state: StatusState)]] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
|
|
|
@ -14,7 +14,7 @@ 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")
|
||||||
|
|
||||||
let mastodonController: MastodonController
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
protocol TuskerNavigationDelegate {
|
protocol TuskerNavigationDelegate: class {
|
||||||
|
|
||||||
var apiController: MastodonController { get }
|
var apiController: MastodonController { get }
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
|
|
||||||
class AccountTableViewCell: UITableViewCell {
|
class AccountTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var delegate: TuskerNavigationDelegate?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var avatarImageView: UIImageView!
|
@IBOutlet weak var avatarImageView: UIImageView!
|
||||||
|
|
|
@ -11,13 +11,13 @@ import Pachyderm
|
||||||
import Gifu
|
import Gifu
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
protocol AttachmentViewDelegate {
|
protocol AttachmentViewDelegate: class {
|
||||||
func showAttachmentsGallery(startingAt index: Int)
|
func showAttachmentsGallery(startingAt index: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentView: UIImageView, GIFAnimatable {
|
class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
|
|
||||||
var delegate: AttachmentViewDelegate?
|
weak var delegate: AttachmentViewDelegate?
|
||||||
|
|
||||||
var playImageView: UIImageView!
|
var playImageView: UIImageView!
|
||||||
|
|
||||||
|
@ -71,8 +71,8 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImage() {
|
func loadImage() {
|
||||||
ImageCache.attachments.get(attachment.url) { (data) in
|
ImageCache.attachments.get(attachment.url) { [weak self] (data) in
|
||||||
guard let data = data else { return }
|
guard let self = self, let data = data else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if self.attachment.url.pathExtension == "gif" {
|
if self.attachment.url.pathExtension == "gif" {
|
||||||
self.animate(withGIFData: data)
|
self.animate(withGIFData: data)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
||||||
|
|
||||||
class AttachmentsContainerView: UIView {
|
class AttachmentsContainerView: UIView {
|
||||||
|
|
||||||
var delegate: AttachmentViewDelegate?
|
weak var delegate: AttachmentViewDelegate?
|
||||||
|
|
||||||
var statusID: String!
|
var statusID: String!
|
||||||
var attachments: [Attachment]!
|
var attachments: [Attachment]!
|
||||||
|
|
|
@ -10,14 +10,14 @@ import UIKit
|
||||||
import Photos
|
import Photos
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
protocol ComposeMediaViewDelegate {
|
protocol ComposeMediaViewDelegate: class {
|
||||||
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
||||||
func descriptionTextViewDidChange(_ mediaView: ComposeMediaView)
|
func descriptionTextViewDidChange(_ mediaView: ComposeMediaView)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComposeMediaView: UIView {
|
class ComposeMediaView: UIView {
|
||||||
|
|
||||||
var delegate: ComposeMediaViewDelegate?
|
weak var delegate: ComposeMediaViewDelegate?
|
||||||
|
|
||||||
@IBOutlet weak var imageView: UIImageView!
|
@IBOutlet weak var imageView: UIImageView!
|
||||||
@IBOutlet weak var descriptionTextView: UITextView!
|
@IBOutlet weak var descriptionTextView: UITextView!
|
||||||
|
|
|
@ -15,8 +15,7 @@ private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options:
|
||||||
|
|
||||||
class ContentTextView: LinkTextView {
|
class ContentTextView: LinkTextView {
|
||||||
|
|
||||||
// todo: should be weak
|
weak var navigationDelegate: TuskerNavigationDelegate?
|
||||||
var navigationDelegate: TuskerNavigationDelegate?
|
|
||||||
var mastodonController: MastodonController? { navigationDelegate?.apiController }
|
var mastodonController: MastodonController? { navigationDelegate?.apiController }
|
||||||
|
|
||||||
var defaultFont: UIFont = .systemFont(ofSize: 17)
|
var defaultFont: UIFont = .systemFont(ofSize: 17)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
||||||
|
|
||||||
class HashtagTableViewCell: UITableViewCell {
|
class HashtagTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var delegate: TuskerNavigationDelegate?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
|
|
||||||
@IBOutlet weak var hashtagLabel: UILabel!
|
@IBOutlet weak var hashtagLabel: UILabel!
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import SwiftSoup
|
||||||
|
|
||||||
class ActionNotificationGroupTableViewCell: UITableViewCell {
|
class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var delegate: TuskerNavigationDelegate?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var actionImageView: UIImageView!
|
@IBOutlet weak var actionImageView: UIImageView!
|
||||||
|
@ -27,6 +27,10 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
var authorAvatarURL: URL?
|
var authorAvatarURL: URL?
|
||||||
var updateTimestampWorkItem: DispatchWorkItem?
|
var updateTimestampWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
updateTimestampWorkItem?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
@ -110,7 +114,9 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
delay = nil
|
delay = nil
|
||||||
}
|
}
|
||||||
if let delay = delay {
|
if let delay = delay {
|
||||||
updateTimestampWorkItem = DispatchWorkItem(block: self.updateTimestamp)
|
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||||
|
self.updateTimestamp()
|
||||||
|
}
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||||
} else {
|
} else {
|
||||||
updateTimestampWorkItem = nil
|
updateTimestampWorkItem = nil
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
||||||
|
|
||||||
class FollowNotificationGroupTableViewCell: UITableViewCell {
|
class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var delegate: TuskerNavigationDelegate?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var avatarStackView: UIStackView!
|
@IBOutlet weak var avatarStackView: UIStackView!
|
||||||
|
@ -22,6 +22,10 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var updateTimestampWorkItem: DispatchWorkItem?
|
var updateTimestampWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
updateTimestampWorkItem?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
@ -98,7 +102,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
delay = nil
|
delay = nil
|
||||||
}
|
}
|
||||||
if let delay = delay {
|
if let delay = delay {
|
||||||
updateTimestampWorkItem = DispatchWorkItem {
|
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||||
self.updateTimestamp()
|
self.updateTimestamp()
|
||||||
}
|
}
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
||||||
|
|
||||||
class FollowRequestNotificationTableViewCell: UITableViewCell {
|
class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var delegate: TuskerNavigationDelegate?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var stackView: UIStackView!
|
@IBOutlet weak var stackView: UIStackView!
|
||||||
|
@ -27,6 +27,10 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var updateTimestampWorkItem: DispatchWorkItem?
|
var updateTimestampWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
updateTimestampWorkItem?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
@ -72,7 +76,9 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
delay = nil
|
delay = nil
|
||||||
}
|
}
|
||||||
if let delay = delay {
|
if let delay = delay {
|
||||||
updateTimestampWorkItem = DispatchWorkItem(block: self.updateTimestamp)
|
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||||
|
self.updateTimestamp()
|
||||||
|
}
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||||
} else {
|
} else {
|
||||||
updateTimestampWorkItem = nil
|
updateTimestampWorkItem = nil
|
||||||
|
|
|
@ -15,7 +15,7 @@ protocol ProfileHeaderTableViewCellDelegate: TuskerNavigationDelegate {
|
||||||
|
|
||||||
class ProfileHeaderTableViewCell: UITableViewCell {
|
class ProfileHeaderTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var delegate: ProfileHeaderTableViewCellDelegate?
|
weak var delegate: ProfileHeaderTableViewCellDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var headerImageView: UIImageView!
|
@IBOutlet weak var headerImageView: UIImageView!
|
||||||
|
|
|
@ -16,7 +16,7 @@ protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
|
||||||
|
|
||||||
class BaseStatusTableViewCell: UITableViewCell {
|
class BaseStatusTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
var delegate: StatusTableViewCellDelegate? {
|
weak var delegate: StatusTableViewCellDelegate? {
|
||||||
didSet {
|
didSet {
|
||||||
contentTextView.navigationDelegate = delegate
|
contentTextView.navigationDelegate = delegate
|
||||||
}
|
}
|
||||||
|
@ -100,16 +100,16 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
open func createObserversIfNecessary() {
|
open func createObserversIfNecessary() {
|
||||||
if statusUpdater == nil {
|
if statusUpdater == nil {
|
||||||
statusUpdater = mastodonController.cache.statusSubject
|
statusUpdater = mastodonController.cache.statusSubject
|
||||||
.filter { $0.id == self.statusID }
|
.filter { [unowned self] in $0.id == self.statusID }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(receiveValue: updateStatusState(status:))
|
.sink { [unowned self] in self.updateStatusState(status: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if accountUpdater == nil {
|
if accountUpdater == nil {
|
||||||
accountUpdater = mastodonController.cache.accountSubject
|
accountUpdater = mastodonController.cache.accountSubject
|
||||||
.filter { $0.id == self.accountID }
|
.filter { [unowned self] in $0.id == self.accountID }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(receiveValue: updateUI(account:))
|
.sink { [unowned self] in self.updateUI(account: $0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
rebloggerAccountUpdater?.cancel()
|
rebloggerAccountUpdater?.cancel()
|
||||||
|
updateTimestampWorkItem?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
|
@ -48,9 +49,9 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
if rebloggerAccountUpdater == nil {
|
if rebloggerAccountUpdater == nil {
|
||||||
rebloggerAccountUpdater = mastodonController.cache.accountSubject
|
rebloggerAccountUpdater = mastodonController.cache.accountSubject
|
||||||
.filter { $0.id == self.rebloggerID }
|
.filter { [unowned self] in $0.id == self.rebloggerID }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(receiveValue: updateRebloggerLabel(reblogger:))
|
.sink { [unowned self] in self.updateRebloggerLabel(reblogger: $0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +95,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTimestamp() {
|
func updateTimestamp() {
|
||||||
|
guard superview != nil else { return }
|
||||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
timestampLabel.text = status.createdAt.timeAgoString()
|
timestampLabel.text = status.createdAt.timeAgoString()
|
||||||
|
@ -109,7 +111,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
delay = nil
|
delay = nil
|
||||||
}
|
}
|
||||||
if let delay = delay {
|
if let delay = delay {
|
||||||
updateTimestampWorkItem = DispatchWorkItem {
|
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||||
self.updateTimestamp()
|
self.updateTimestamp()
|
||||||
}
|
}
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||||
|
|
Loading…
Reference in New Issue