forked from shadowfacts/Tusker
Merge branch 'multiple-accounts'
This commit is contained in:
commit
3220436893
|
@ -130,32 +130,32 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Self
|
||||
public func getSelfAccount() -> Request<Account> {
|
||||
public static func getSelfAccount() -> Request<Account> {
|
||||
return Request<Account>(method: .get, path: "/api/v1/accounts/verify_credentials")
|
||||
}
|
||||
|
||||
public func getFavourites() -> Request<[Status]> {
|
||||
public static func getFavourites() -> Request<[Status]> {
|
||||
return Request<[Status]>(method: .get, path: "/api/v1/favourites")
|
||||
}
|
||||
|
||||
public func getRelationships(accounts: [String]? = nil) -> Request<[Relationship]> {
|
||||
public static func getRelationships(accounts: [String]? = nil) -> Request<[Relationship]> {
|
||||
return Request<[Relationship]>(method: .get, path: "/api/v1/accounts/relationships", queryParameters: "id" => accounts)
|
||||
}
|
||||
|
||||
public func getInstance() -> Request<Instance> {
|
||||
public static func getInstance() -> Request<Instance> {
|
||||
return Request<Instance>(method: .get, path: "/api/v1/instance")
|
||||
}
|
||||
|
||||
public func getCustomEmoji() -> Request<[Emoji]> {
|
||||
public static func getCustomEmoji() -> Request<[Emoji]> {
|
||||
return Request<[Emoji]>(method: .get, path: "/api/v1/custom_emojis")
|
||||
}
|
||||
|
||||
// MARK: - Accounts
|
||||
public func getAccount(id: String) -> Request<Account> {
|
||||
public static func getAccount(id: String) -> Request<Account> {
|
||||
return Request<Account>(method: .get, path: "/api/v1/accounts/\(id)")
|
||||
}
|
||||
|
||||
public func searchForAccount(query: String, limit: Int? = nil, following: Bool? = nil) -> Request<[Account]> {
|
||||
public static func searchForAccount(query: String, limit: Int? = nil, following: Bool? = nil) -> Request<[Account]> {
|
||||
return Request<[Account]>(method: .get, path: "/api/v1/accounts/search", queryParameters: [
|
||||
"q" => query,
|
||||
"limit" => limit,
|
||||
|
@ -164,32 +164,32 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Blocks
|
||||
public func getBlocks() -> Request<[Account]> {
|
||||
public static func getBlocks() -> Request<[Account]> {
|
||||
return Request<[Account]>(method: .get, path: "/api/v1/blocks")
|
||||
}
|
||||
|
||||
public func getDomainBlocks() -> Request<[String]> {
|
||||
public static func getDomainBlocks() -> Request<[String]> {
|
||||
return Request<[String]>(method: .get, path: "api/v1/domain_blocks")
|
||||
}
|
||||
|
||||
public func block(domain: String) -> Request<Empty> {
|
||||
public static func block(domain: String) -> Request<Empty> {
|
||||
return Request<Empty>(method: .post, path: "/api/v1/domain_blocks", body: .parameters([
|
||||
"domain" => domain
|
||||
]))
|
||||
}
|
||||
|
||||
public func unblock(domain: String) -> Request<Empty> {
|
||||
public static func unblock(domain: String) -> Request<Empty> {
|
||||
return Request<Empty>(method: .delete, path: "/api/v1/domain_blocks", body: .parameters([
|
||||
"domain" => domain
|
||||
]))
|
||||
}
|
||||
|
||||
// MARK: - Filters
|
||||
public func getFilters() -> Request<[Filter]> {
|
||||
public static func getFilters() -> Request<[Filter]> {
|
||||
return Request<[Filter]>(method: .get, path: "/api/v1/filters")
|
||||
}
|
||||
|
||||
public func createFilter(phrase: String, context: [Filter.Context], irreversible: Bool? = nil, wholeWord: Bool? = nil, expiresAt: Date? = nil) -> Request<Filter> {
|
||||
public static func createFilter(phrase: String, context: [Filter.Context], irreversible: Bool? = nil, wholeWord: Bool? = nil, expiresAt: Date? = nil) -> Request<Filter> {
|
||||
return Request<Filter>(method: .post, path: "/api/v1/filters", body: .parameters([
|
||||
"phrase" => phrase,
|
||||
"irreversible" => irreversible,
|
||||
|
@ -198,40 +198,40 @@ public class Client {
|
|||
] + "context" => context.contextStrings))
|
||||
}
|
||||
|
||||
public func getFilter(id: String) -> Request<Filter> {
|
||||
public static func getFilter(id: String) -> Request<Filter> {
|
||||
return Request<Filter>(method: .get, path: "/api/v1/filters/\(id)")
|
||||
}
|
||||
|
||||
// MARK: - Follows
|
||||
public func getFollowRequests(range: RequestRange = .default) -> Request<[Account]> {
|
||||
public static func getFollowRequests(range: RequestRange = .default) -> Request<[Account]> {
|
||||
var request = Request<[Account]>(method: .get, path: "/api/v1/follow_requests")
|
||||
request.range = range
|
||||
return request
|
||||
}
|
||||
|
||||
public func getFollowSuggestions() -> Request<[Account]> {
|
||||
public static func getFollowSuggestions() -> Request<[Account]> {
|
||||
return Request<[Account]>(method: .get, path: "/api/v1/suggestions")
|
||||
}
|
||||
|
||||
public func followRemote(acct: String) -> Request<Account> {
|
||||
public static func followRemote(acct: String) -> Request<Account> {
|
||||
return Request<Account>(method: .post, path: "/api/v1/follows", body: .parameters(["uri" => acct]))
|
||||
}
|
||||
|
||||
// MARK: - Lists
|
||||
public func getLists() -> Request<[List]> {
|
||||
public static func getLists() -> Request<[List]> {
|
||||
return Request<[List]>(method: .get, path: "/api/v1/lists")
|
||||
}
|
||||
|
||||
public func getList(id: String) -> Request<List> {
|
||||
public static func getList(id: String) -> Request<List> {
|
||||
return Request<List>(method: .get, path: "/api/v1/lists/\(id)")
|
||||
}
|
||||
|
||||
public func createList(title: String) -> Request<List> {
|
||||
public static func createList(title: String) -> Request<List> {
|
||||
return Request<List>(method: .post, path: "/api/v1/lists", body: .parameters(["title" => title]))
|
||||
}
|
||||
|
||||
// MARK: - Media
|
||||
public func upload(attachment: FormAttachment, description: String? = nil, focus: (Float, Float)? = nil) -> Request<Attachment> {
|
||||
public static func upload(attachment: FormAttachment, description: String? = nil, focus: (Float, Float)? = nil) -> Request<Attachment> {
|
||||
return Request<Attachment>(method: .post, path: "/api/v1/media", body: .formData([
|
||||
"description" => description,
|
||||
"focus" => focus
|
||||
|
@ -239,14 +239,14 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Mutes
|
||||
public func getMutes(range: RequestRange) -> Request<[Account]> {
|
||||
public static func getMutes(range: RequestRange) -> Request<[Account]> {
|
||||
var request = Request<[Account]>(method: .get, path: "/api/v1/mutes")
|
||||
request.range = range
|
||||
return request
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
public func getNotifications(excludeTypes: [Notification.Kind], range: RequestRange = .default) -> Request<[Notification]> {
|
||||
public static func getNotifications(excludeTypes: [Notification.Kind], range: RequestRange = .default) -> Request<[Notification]> {
|
||||
var request = Request<[Notification]>(method: .get, path: "/api/v1/notifications", queryParameters:
|
||||
"exclude_types" => excludeTypes.map { $0.rawValue }
|
||||
)
|
||||
|
@ -254,16 +254,16 @@ public class Client {
|
|||
return request
|
||||
}
|
||||
|
||||
public func clearNotifications() -> Request<Empty> {
|
||||
public static func clearNotifications() -> Request<Empty> {
|
||||
return Request<Empty>(method: .post, path: "/api/v1/notifications/clear")
|
||||
}
|
||||
|
||||
// MARK: - Reports
|
||||
public func getReports() -> Request<[Report]> {
|
||||
public static func getReports() -> Request<[Report]> {
|
||||
return Request<[Report]>(method: .get, path: "/api/v1/reports")
|
||||
}
|
||||
|
||||
public func report(account: Account, statuses: [Status], comment: String) -> Request<Report> {
|
||||
public static func report(account: Account, statuses: [Status], comment: String) -> Request<Report> {
|
||||
return Request<Report>(method: .post, path: "/api/v1/reports", body: .parameters([
|
||||
"account_id" => account.id,
|
||||
"comment" => comment
|
||||
|
@ -271,7 +271,7 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Search
|
||||
public func search(query: String, resolve: Bool? = nil, limit: Int? = nil) -> Request<SearchResults> {
|
||||
public static func search(query: String, resolve: Bool? = nil, limit: Int? = nil) -> Request<SearchResults> {
|
||||
return Request<SearchResults>(method: .get, path: "/api/v2/search", queryParameters: [
|
||||
"q" => query,
|
||||
"resolve" => resolve,
|
||||
|
@ -280,11 +280,11 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Statuses
|
||||
public func getStatus(id: String) -> Request<Status> {
|
||||
public static func getStatus(id: String) -> Request<Status> {
|
||||
return Request<Status>(method: .get, path: "/api/v1/statuses/\(id)")
|
||||
}
|
||||
|
||||
public func createStatus(text: String,
|
||||
public static func createStatus(text: String,
|
||||
contentType: StatusContentType = .plain,
|
||||
inReplyTo: String? = nil,
|
||||
media: [Attachment]? = nil,
|
||||
|
@ -304,13 +304,13 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Timelines
|
||||
public func getStatuses(timeline: Timeline, range: RequestRange = .default) -> Request<[Status]> {
|
||||
public static func getStatuses(timeline: Timeline, range: RequestRange = .default) -> Request<[Status]> {
|
||||
return timeline.request(range: range)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Bookmarks
|
||||
public func getBookmarks(range: RequestRange = .default) -> Request<[Status]> {
|
||||
public static func getBookmarks(range: RequestRange = .default) -> Request<[Status]> {
|
||||
var request = Request<[Status]>(method: .get, path: "/api/v1/bookmarks")
|
||||
request.range = range
|
||||
return request
|
||||
|
|
|
@ -122,6 +122,7 @@
|
|||
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; };
|
||||
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */; };
|
||||
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 */; };
|
||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
||||
|
@ -156,10 +157,9 @@
|
|||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
|
||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
||||
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; };
|
||||
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedDataManager.swift */; };
|
||||
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */; };
|
||||
D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */; };
|
||||
D6945C3623AC6C09005C403C /* SavedInstancesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3523AC6C09005C403C /* SavedInstancesManager.swift */; };
|
||||
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */; };
|
||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */; };
|
||||
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; };
|
||||
|
@ -177,6 +177,7 @@
|
|||
D6A5BB2F23BBAC97003BF21D /* DelegatingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */; };
|
||||
D6A5BB3123BBAD87003BF21D /* JSONResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */; };
|
||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
|
||||
D6AC956723C4347E008C9946 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* SceneDelegate.swift */; };
|
||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; };
|
||||
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */; };
|
||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; };
|
||||
|
@ -217,6 +218,8 @@
|
|||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.swift */; };
|
||||
D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26221603F8B006A8599 /* CharacterCounter.swift */; };
|
||||
D6E6F26521604242006A8599 /* CharacterCounterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26421604242006A8599 /* CharacterCounterTests.swift */; };
|
||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
||||
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */; };
|
||||
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F1F84C2193B56E00F5FE67 /* Cache.swift */; };
|
||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */; };
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
|
||||
|
@ -394,6 +397,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -429,10 +433,9 @@
|
|||
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = "<group>"; };
|
||||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
||||
D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtagsManager.swift; sourceTree = "<group>"; };
|
||||
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedDataManager.swift; sourceTree = "<group>"; };
|
||||
D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSavedHashtagViewController.swift; sourceTree = "<group>"; };
|
||||
D6945C3523AC6C09005C403C /* SavedInstancesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedInstancesManager.swift; sourceTree = "<group>"; };
|
||||
D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FindInstanceViewController.swift; path = Tusker/Screens/FindInstanceViewController.swift; sourceTree = SOURCE_ROOT; };
|
||||
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = "<group>"; };
|
||||
|
@ -450,6 +453,7 @@
|
|||
D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatingResponse.swift; sourceTree = "<group>"; };
|
||||
D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONResponse.swift; sourceTree = "<group>"; };
|
||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
|
||||
D6AC956623C4347E008C9946 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = "<group>"; };
|
||||
D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMesasgeActivity.swift; sourceTree = "<group>"; };
|
||||
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = "<group>"; };
|
||||
|
@ -494,6 +498,8 @@
|
|||
D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = "<group>"; };
|
||||
D6E6F26221603F8B006A8599 /* CharacterCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounter.swift; sourceTree = "<group>"; };
|
||||
D6E6F26421604242006A8599 /* CharacterCounterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounterTests.swift; sourceTree = "<group>"; };
|
||||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
||||
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+MastodonController.swift"; sourceTree = "<group>"; };
|
||||
D6F1F84C2193B56E00F5FE67 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
|
||||
D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = "<group>"; };
|
||||
D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = "<group>"; };
|
||||
|
@ -967,6 +973,8 @@
|
|||
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */,
|
||||
0450531E22B0097E00100BA2 /* Timline+UI.swift */,
|
||||
D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */,
|
||||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */,
|
||||
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1061,6 +1069,7 @@
|
|||
children = (
|
||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */,
|
||||
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
|
||||
D64BC19123C271D9000D0238 /* MastodonActivity.swift */,
|
||||
D6AEBB4623216B0C00E5038B /* Account Activities */,
|
||||
D627943323A5523800D38C68 /* Status Activities */,
|
||||
);
|
||||
|
@ -1181,10 +1190,10 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||
D6AC956623C4347E008C9946 /* SceneDelegate.swift */,
|
||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||
D627FF75217E923E00CC0648 /* DraftsManager.swift */,
|
||||
D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */,
|
||||
D6945C3523AC6C09005C403C /* SavedInstancesManager.swift */,
|
||||
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
||||
D6028B9A2150811100F223B9 /* MastodonCache.swift */,
|
||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
||||
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
||||
|
@ -1620,6 +1629,7 @@
|
|||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */,
|
||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
||||
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */,
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */,
|
||||
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */,
|
||||
|
@ -1631,7 +1641,6 @@
|
|||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
||||
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
||||
D6945C3623AC6C09005C403C /* SavedInstancesManager.swift in Sources */,
|
||||
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */,
|
||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */,
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||
|
@ -1642,6 +1651,7 @@
|
|||
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */,
|
||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
|
||||
D6AC956723C4347E008C9946 /* SceneDelegate.swift in Sources */,
|
||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */,
|
||||
|
@ -1676,15 +1686,17 @@
|
|||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
||||
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
||||
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */,
|
||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
||||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
|
||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */,
|
||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */,
|
||||
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */,
|
||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */,
|
||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class AccountActivity: UIActivity {
|
||||
class AccountActivity: MastodonActivity {
|
||||
|
||||
override class var activityCategory: UIActivity.Category {
|
||||
return .action
|
||||
|
|
|
@ -28,9 +28,9 @@ class FollowAccountActivity: AccountActivity {
|
|||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
|
||||
let request = Account.follow(account.id)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(relationship, _) = response {
|
||||
MastodonCache.add(relationship: relationship)
|
||||
self.mastodonController.cache.add(relationship: relationship)
|
||||
} else {
|
||||
// todo: display error message
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
|
|
|
@ -28,7 +28,7 @@ class SendMessageActivity: AccountActivity {
|
|||
override var activityViewController: UIViewController? {
|
||||
guard let account = account else { return nil }
|
||||
|
||||
return UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct))
|
||||
return UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, mastodonController: mastodonController))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ class UnfollowAccountActivity: AccountActivity {
|
|||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
|
||||
let request = Account.unfollow(account.id)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(relationship, _) = response {
|
||||
MastodonCache.add(relationship: relationship)
|
||||
self.mastodonController.cache.add(relationship: relationship)
|
||||
} else {
|
||||
// todo: display error message
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// MastodonActivity.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/5/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class MastodonActivity: UIActivity {
|
||||
var mastodonController: MastodonController {
|
||||
let scene = UIApplication.shared.activeOrBackgroundScene!
|
||||
return scene.session.mastodonController!
|
||||
}
|
||||
}
|
|
@ -27,9 +27,9 @@ class BookmarkStatusActivity: StatusActivity {
|
|||
guard let status = status else { return }
|
||||
|
||||
let request = Status.bookmark(status)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(status, _) = response {
|
||||
MastodonCache.add(status: status)
|
||||
self.mastodonController.cache.add(status: status)
|
||||
} else {
|
||||
// todo: display error message
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
|
|
|
@ -26,9 +26,9 @@ class PinStatusActivity: StatusActivity {
|
|||
guard let status = status else { return }
|
||||
|
||||
let request = Status.pin(status)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(status, _) = response {
|
||||
MastodonCache.add(status: status)
|
||||
self.mastodonController.cache.add(status: status)
|
||||
} else {
|
||||
// todo: display error message
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class StatusActivity: UIActivity {
|
||||
class StatusActivity: MastodonActivity {
|
||||
|
||||
override class var activityCategory: UIActivity.Category {
|
||||
return .action
|
||||
|
|
|
@ -27,9 +27,9 @@ class UnbookmarkStatusActivity: StatusActivity {
|
|||
guard let status = status else { return }
|
||||
|
||||
let request = Status.unbookmark(status)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(status, _) = response {
|
||||
MastodonCache.add(status: status)
|
||||
self.mastodonController.cache.add(status: status)
|
||||
} else {
|
||||
// todo: display error message
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
|
|
|
@ -26,9 +26,9 @@ class UnpinStatusActivity: StatusActivity {
|
|||
guard let status = status else { return }
|
||||
|
||||
let request = Status.unpin(status)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(status, _) = response {
|
||||
MastodonCache.add(status: status)
|
||||
self.mastodonController.cache.add(status: status)
|
||||
} else {
|
||||
// todo: display error message
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
|
|
|
@ -11,117 +11,9 @@ import UIKit
|
|||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
AppShortcutItem.createItems(for: application)
|
||||
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
|
||||
if LocalData.shared.onboardingComplete {
|
||||
showAppUI()
|
||||
} else {
|
||||
showOnboardingUI()
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(onUserLoggedOut), name: .userLoggedOut, object: nil)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(themePrefChanged), name: .themePreferenceChanged, object: nil)
|
||||
themePrefChanged()
|
||||
|
||||
window!.makeKeyAndVisible()
|
||||
|
||||
if let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem {
|
||||
_ = AppShortcutItem.handle(shortcutItem)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||
if url.host == "x-callback-url" {
|
||||
return XCBManager.handle(url: url)
|
||||
} else if var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
let tabBarController = window!.rootViewController as? MainTabBarViewController,
|
||||
let exploreNavController = tabBarController.getTabController(tab: .explore) as? UINavigationController,
|
||||
let exploreController = exploreNavController.viewControllers.first as? ExploreViewController {
|
||||
|
||||
tabBarController.select(tab: .explore)
|
||||
exploreNavController.popToRootViewController(animated: false)
|
||||
|
||||
exploreController.loadViewIfNeeded()
|
||||
exploreController.searchController.isActive = true
|
||||
|
||||
components.scheme = "https"
|
||||
let query = components.url!.absoluteString
|
||||
exploreController.searchController.searchBar.text = query
|
||||
exploreController.resultsController.performSearch(query: query)
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
return userActivity.handleResume()
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
|
||||
completionHandler(AppShortcutItem.handle(shortcutItem))
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
Preferences.save()
|
||||
DraftsManager.save()
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
func showAppUI() {
|
||||
MastodonController.createClient()
|
||||
MastodonController.getOwnAccount()
|
||||
MastodonController.getOwnInstance()
|
||||
|
||||
let tabBarController = MainTabBarViewController()
|
||||
window!.rootViewController = tabBarController
|
||||
}
|
||||
|
||||
func showOnboardingUI() {
|
||||
let onboarding = OnboardingViewController()
|
||||
onboarding.onboardingDelegate = self
|
||||
window!.rootViewController = onboarding
|
||||
}
|
||||
|
||||
@objc func onUserLoggedOut() {
|
||||
showOnboardingUI()
|
||||
}
|
||||
|
||||
@objc func themePrefChanged() {
|
||||
window?.overrideUserInterfaceStyle = Preferences.shared.theme
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AppDelegate: OnboardingViewControllerDelegate {
|
||||
func didFinishOnboarding() {
|
||||
LocalData.shared.onboardingComplete = true
|
||||
showAppUI()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,63 +11,86 @@ import Pachyderm
|
|||
|
||||
class MastodonController {
|
||||
|
||||
static var client: Client!
|
||||
static private(set) var all = [LocalData.UserAccountInfo: MastodonController]()
|
||||
|
||||
static var account: Account!
|
||||
static var instance: Instance!
|
||||
@available(*, message: "do something less dumb")
|
||||
static var first: MastodonController { all.first!.value }
|
||||
|
||||
private init() {}
|
||||
|
||||
static func createClient() {
|
||||
guard let url = LocalData.shared.instanceURL else { fatalError("Can't connect without instance URL") }
|
||||
|
||||
client = Client(baseURL: url)
|
||||
|
||||
client.clientID = LocalData.shared.clientID
|
||||
client.clientSecret = LocalData.shared.clientSecret
|
||||
client.accessToken = LocalData.shared.accessToken
|
||||
static func getForAccount(_ account: LocalData.UserAccountInfo) -> MastodonController {
|
||||
if let controller = all[account] {
|
||||
return controller
|
||||
} else {
|
||||
let controller = MastodonController(instanceURL: account.instanceURL)
|
||||
controller.accountInfo = account
|
||||
controller.client.clientID = account.clientID
|
||||
controller.client.clientSecret = account.clientSecret
|
||||
controller.client.accessToken = account.accessToken
|
||||
all[account] = controller
|
||||
return controller
|
||||
}
|
||||
}
|
||||
|
||||
static func registerApp(completion: @escaping () -> Void) {
|
||||
guard LocalData.shared.clientID == nil,
|
||||
LocalData.shared.clientSecret == nil else {
|
||||
completion()
|
||||
private(set) lazy var cache = MastodonCache(mastodonController: self)
|
||||
|
||||
let instanceURL: URL
|
||||
private(set) var accountInfo: LocalData.UserAccountInfo?
|
||||
|
||||
let client: Client!
|
||||
|
||||
var account: Account!
|
||||
var instance: Instance!
|
||||
|
||||
init(instanceURL: URL) {
|
||||
self.instanceURL = instanceURL
|
||||
self.accountInfo = nil
|
||||
self.client = Client(baseURL: instanceURL)
|
||||
}
|
||||
|
||||
func run<Result>(_ request: Request<Result>, completion: @escaping Client.Callback<Result>) {
|
||||
client.run(request, completion: completion)
|
||||
}
|
||||
|
||||
func registerApp(completion: @escaping (_ clientID: String, _ clientSecret: String) -> Void) {
|
||||
guard client.clientID == nil,
|
||||
client.clientSecret == nil else {
|
||||
|
||||
completion(client.clientID!, client.clientSecret!)
|
||||
return
|
||||
}
|
||||
|
||||
client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow]) { response in
|
||||
guard case let .success(app, _) = response else { fatalError() }
|
||||
LocalData.shared.clientID = app.clientID
|
||||
LocalData.shared.clientSecret = app.clientSecret
|
||||
completion()
|
||||
self.client.clientID = app.clientID
|
||||
self.client.clientSecret = app.clientSecret
|
||||
completion(app.clientID, app.clientSecret)
|
||||
}
|
||||
}
|
||||
|
||||
static func authorize(authorizationCode: String, completion: @escaping () -> Void) {
|
||||
func authorize(authorizationCode: String, completion: @escaping (_ accessToken: String) -> Void) {
|
||||
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
|
||||
guard case let .success(settings, _) = response else { fatalError() }
|
||||
LocalData.shared.accessToken = settings.accessToken
|
||||
completion()
|
||||
self.client.accessToken = settings.accessToken
|
||||
completion(settings.accessToken)
|
||||
}
|
||||
}
|
||||
|
||||
static func getOwnAccount(completion: ((Account) -> Void)? = nil) {
|
||||
func getOwnAccount(completion: ((Account) -> Void)? = nil) {
|
||||
if account != nil {
|
||||
completion?(account)
|
||||
} else {
|
||||
let request = client.getSelfAccount()
|
||||
client.run(request) { response in
|
||||
let request = Client.getSelfAccount()
|
||||
run(request) { response in
|
||||
guard case let .success(account, _) = response else { fatalError() }
|
||||
self.account = account
|
||||
MastodonCache.add(account: account)
|
||||
self.cache.add(account: account)
|
||||
completion?(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func getOwnInstance() {
|
||||
let request = client.getInstance()
|
||||
client.run(request) { (response) in
|
||||
func getOwnInstance() {
|
||||
let request = Client.getInstance()
|
||||
run(request) { (response) in
|
||||
guard case let .success(instance, _) = response else { fatalError() }
|
||||
self.instance = instance
|
||||
}
|
||||
|
|
|
@ -39,8 +39,8 @@ class DraftsManager: Codable {
|
|||
return drafts.sorted(by: { $0.lastModified > $1.lastModified })
|
||||
}
|
||||
|
||||
func create(text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment]) -> Draft {
|
||||
let draft = Draft(text: text, contentWarning: contentWarning, inReplyToID: inReplyToID, attachments: attachments)
|
||||
func create(accountID: String, text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment]) -> Draft {
|
||||
let draft = Draft(accountID: accountID, text: text, contentWarning: contentWarning, inReplyToID: inReplyToID, attachments: attachments)
|
||||
drafts.append(draft)
|
||||
return draft
|
||||
}
|
||||
|
@ -55,14 +55,16 @@ class DraftsManager: Codable {
|
|||
extension DraftsManager {
|
||||
class Draft: Codable, Equatable {
|
||||
let id: UUID
|
||||
private(set) var accountID: String
|
||||
private(set) var text: String
|
||||
private(set) var contentWarning: String?
|
||||
private(set) var attachments: [DraftAttachment]
|
||||
private(set) var inReplyToID: String?
|
||||
private(set) var lastModified: Date
|
||||
|
||||
init(text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment], lastModified: Date = Date()) {
|
||||
init(accountID: String, text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment], lastModified: Date = Date()) {
|
||||
self.id = UUID()
|
||||
self.accountID = accountID
|
||||
self.text = text
|
||||
self.contentWarning = contentWarning
|
||||
self.inReplyToID = inReplyToID
|
||||
|
@ -70,7 +72,8 @@ extension DraftsManager {
|
|||
self.lastModified = lastModified
|
||||
}
|
||||
|
||||
func update(text: String, contentWarning: String?, attachments: [DraftAttachment]) {
|
||||
func update(accountID: String, text: String, contentWarning: String?, attachments: [DraftAttachment]) {
|
||||
self.accountID = accountID
|
||||
self.text = text
|
||||
self.contentWarning = contentWarning
|
||||
self.lastModified = Date()
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// UIApplication+Scenes.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/7/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIApplication {
|
||||
|
||||
var activeScene: UIScene? {
|
||||
connectedScenes.first { $0.activationState == .foregroundActive }
|
||||
}
|
||||
|
||||
var backgroundScene: UIScene? {
|
||||
connectedScenes.first { $0.activationState == .background }
|
||||
}
|
||||
|
||||
var activeOrBackgroundScene: UIScene? {
|
||||
activeScene ?? backgroundScene
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// UISceneSession+MastodonController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/7/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UISceneSession {
|
||||
|
||||
var mastodonController: MastodonController? {
|
||||
get {
|
||||
return userInfo?["mastodonController"] as? MastodonController
|
||||
}
|
||||
set {
|
||||
if let newValue = newValue {
|
||||
if userInfo == nil {
|
||||
userInfo = ["mastodonController": newValue]
|
||||
} else {
|
||||
userInfo!["mastodonController"] = newValue
|
||||
}
|
||||
} else {
|
||||
if userInfo != nil {
|
||||
userInfo?.removeValue(forKey: "mastodonController")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,25 +2,6 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-timeline</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.check-notifications</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.check-mentions</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.new-post</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.search</string>
|
||||
</array>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
@ -52,14 +33,52 @@
|
|||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Post videos from the camera.</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Post photos and videos from the camera.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Post videos from the camera.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Save photos directly from other people's posts.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Post photos from the photo library.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-timeline</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.check-notifications</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.check-mentions</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.new-post</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.search</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>main-scene</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class LocalData {
|
||||
class LocalData: ObservableObject {
|
||||
|
||||
static let shared = LocalData()
|
||||
|
||||
|
@ -18,68 +19,130 @@ class LocalData {
|
|||
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING") {
|
||||
defaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).uitesting")!
|
||||
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") {
|
||||
defaults.set(true, forKey: onboardingCompleteKey)
|
||||
defaults.set(URL(string: "http://localhost:8080")!, forKey: instanceURLKey)
|
||||
defaults.set("client_id", forKey: clientIDKey)
|
||||
defaults.set("client_secret", forKey: clientSecretKey)
|
||||
defaults.set("access_token", forKey: accessTokenKey)
|
||||
accounts = [
|
||||
UserAccountInfo(
|
||||
id: UUID().uuidString,
|
||||
instanceURL: URL(string: "http://localhost:8080")!,
|
||||
clientID: "client_id",
|
||||
clientSecret: "client_secret",
|
||||
username: "admin",
|
||||
accessToken: "access_token")
|
||||
]
|
||||
}
|
||||
} else {
|
||||
defaults = UserDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
private let onboardingCompleteKey = "onboardingComplete"
|
||||
private let accountsKey = "accounts"
|
||||
var accounts: [UserAccountInfo] {
|
||||
get {
|
||||
if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
|
||||
return array.compactMap { (info) in
|
||||
guard let id = info["id"],
|
||||
let instanceURL = info["instanceURL"],
|
||||
let url = URL(string: instanceURL),
|
||||
let clientId = info["clientID"],
|
||||
let secret = info["clientSecret"],
|
||||
let username = info["username"],
|
||||
let accessToken = info["accessToken"] else {
|
||||
return nil
|
||||
}
|
||||
return UserAccountInfo(id: id, instanceURL: url, clientID: clientId, clientSecret: secret, username: username, accessToken: accessToken)
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
set {
|
||||
objectWillChange.send()
|
||||
let array = newValue.map { (info) in
|
||||
return [
|
||||
"id": info.id,
|
||||
"instanceURL": info.instanceURL.absoluteString,
|
||||
"clientID": info.clientID,
|
||||
"clientSecret": info.clientSecret,
|
||||
"username": info.username,
|
||||
"accessToken": info.accessToken
|
||||
]
|
||||
}
|
||||
defaults.set(array, forKey: accountsKey)
|
||||
}
|
||||
}
|
||||
|
||||
private let mostRecentAccountKey = "mostRecentAccount"
|
||||
private var mostRecentAccount: String? {
|
||||
get {
|
||||
return defaults.string(forKey: mostRecentAccountKey)
|
||||
}
|
||||
set {
|
||||
objectWillChange.send()
|
||||
defaults.set(newValue, forKey: mostRecentAccountKey)
|
||||
}
|
||||
}
|
||||
|
||||
var onboardingComplete: Bool {
|
||||
get {
|
||||
return defaults.bool(forKey: onboardingCompleteKey)
|
||||
}
|
||||
set {
|
||||
defaults.set(newValue, forKey: onboardingCompleteKey)
|
||||
}
|
||||
return !accounts.isEmpty
|
||||
}
|
||||
|
||||
private let instanceURLKey = "instanceURL"
|
||||
var instanceURL: URL? {
|
||||
get {
|
||||
return defaults.url(forKey: instanceURLKey)
|
||||
}
|
||||
set {
|
||||
defaults.set(newValue, forKey: instanceURLKey)
|
||||
func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String, accessToken: String) -> UserAccountInfo {
|
||||
var accounts = self.accounts
|
||||
if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) {
|
||||
accounts.remove(at: index)
|
||||
}
|
||||
let id = UUID().uuidString
|
||||
let info = UserAccountInfo(id: id, instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken)
|
||||
accounts.append(info)
|
||||
self.accounts = accounts
|
||||
return info
|
||||
}
|
||||
|
||||
private let clientIDKey = "clientID"
|
||||
var clientID: String? {
|
||||
get {
|
||||
return defaults.string(forKey: clientIDKey)
|
||||
}
|
||||
set {
|
||||
defaults.set(newValue, forKey: clientIDKey)
|
||||
}
|
||||
func removeAccount(_ info: UserAccountInfo) {
|
||||
accounts.removeAll(where: { $0.id == info.id })
|
||||
}
|
||||
|
||||
private let clientSecretKey = "clientSecret"
|
||||
var clientSecret: String? {
|
||||
get {
|
||||
return defaults.string(forKey: clientSecretKey)
|
||||
}
|
||||
set {
|
||||
defaults.set(newValue, forKey: clientSecretKey)
|
||||
}
|
||||
func getAccount(id: String) -> UserAccountInfo? {
|
||||
return accounts.first(where: { $0.id == id })
|
||||
}
|
||||
|
||||
private let accessTokenKey = "accessToken"
|
||||
var accessToken: String? {
|
||||
get {
|
||||
return defaults.string(forKey: accessTokenKey)
|
||||
func getMostRecentAccount() -> UserAccountInfo? {
|
||||
guard onboardingComplete else { return nil }
|
||||
let mostRecent: UserAccountInfo?
|
||||
if let id = mostRecentAccount {
|
||||
mostRecent = accounts.first { $0.id == id }
|
||||
} else {
|
||||
mostRecent = nil
|
||||
}
|
||||
set {
|
||||
defaults.set(newValue, forKey: accessTokenKey)
|
||||
return mostRecent ?? accounts.first!
|
||||
}
|
||||
|
||||
func setMostRecentAccount(_ account: UserAccountInfo?) {
|
||||
mostRecentAccount = account?.id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension LocalData {
|
||||
struct UserAccountInfo: Equatable, Hashable {
|
||||
let id: String
|
||||
let instanceURL: URL
|
||||
let clientID: String
|
||||
let clientSecret: String
|
||||
let username: 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 {
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -12,20 +12,26 @@ import Pachyderm
|
|||
|
||||
class MastodonCache {
|
||||
|
||||
private static var statuses = CachedDictionary<Status>(name: "Statuses")
|
||||
private static var accounts = CachedDictionary<Account>(name: "Accounts")
|
||||
private static var relationships = CachedDictionary<Relationship>(name: "Relationships")
|
||||
private static var notifications = CachedDictionary<Pachyderm.Notification>(name: "Notifications")
|
||||
private var statuses = CachedDictionary<Status>(name: "Statuses")
|
||||
private var accounts = CachedDictionary<Account>(name: "Accounts")
|
||||
private var relationships = CachedDictionary<Relationship>(name: "Relationships")
|
||||
private var notifications = CachedDictionary<Pachyderm.Notification>(name: "Notifications")
|
||||
|
||||
static let statusSubject = PassthroughSubject<Status, Never>()
|
||||
static let accountSubject = PassthroughSubject<Account, Never>()
|
||||
let statusSubject = PassthroughSubject<Status, Never>()
|
||||
let accountSubject = PassthroughSubject<Account, Never>()
|
||||
|
||||
weak var mastodonController: MastodonController?
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
}
|
||||
|
||||
// MARK: - Statuses
|
||||
static func status(for id: String) -> Status? {
|
||||
func status(for id: String) -> Status? {
|
||||
return statuses[id]
|
||||
}
|
||||
|
||||
static func set(status: Status, for id: String) {
|
||||
func set(status: Status, for id: String) {
|
||||
statuses[id] = status
|
||||
add(account: status.account)
|
||||
if let reblog = status.reblog {
|
||||
|
@ -36,100 +42,109 @@ class MastodonCache {
|
|||
statusSubject.send(status)
|
||||
}
|
||||
|
||||
static func status(for id: String, completion: @escaping (Status?) -> Void) {
|
||||
let request = MastodonController.client.getStatus(id: id)
|
||||
MastodonController.client.run(request) { response in
|
||||
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)
|
||||
mastodonController.run(request) { response in
|
||||
guard case let .success(status, _) = response else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
set(status: status, for: id)
|
||||
self.set(status: status, for: id)
|
||||
completion(status)
|
||||
}
|
||||
}
|
||||
|
||||
static func add(status: Status) {
|
||||
func add(status: Status) {
|
||||
set(status: status, for: status.id)
|
||||
}
|
||||
|
||||
static func addAll(statuses: [Status]) {
|
||||
func addAll(statuses: [Status]) {
|
||||
statuses.forEach(add)
|
||||
}
|
||||
|
||||
// MARK: - Accounts
|
||||
static func account(for id: String) -> Account? {
|
||||
func account(for id: String) -> Account? {
|
||||
return accounts[id]
|
||||
}
|
||||
|
||||
static func set(account: Account, for id: String) {
|
||||
func set(account: Account, for id: String) {
|
||||
accounts[id] = account
|
||||
accountSubject.send(account)
|
||||
}
|
||||
|
||||
static func account(for id: String, completion: @escaping (Account?) -> Void) {
|
||||
let request = MastodonController.client.getAccount(id: id)
|
||||
MastodonController.client.run(request) { response in
|
||||
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)
|
||||
mastodonController.run(request) { response in
|
||||
guard case let .success(account, _) = response else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
set(account: account, for: account.id)
|
||||
self.set(account: account, for: account.id)
|
||||
completion(account)
|
||||
}
|
||||
}
|
||||
|
||||
static func add(account: Account) {
|
||||
func add(account: Account) {
|
||||
set(account: account, for: account.id)
|
||||
}
|
||||
|
||||
static func addAll(accounts: [Account]) {
|
||||
func addAll(accounts: [Account]) {
|
||||
accounts.forEach(add)
|
||||
}
|
||||
|
||||
// MARK: - Relationships
|
||||
static func relationship(for id: String) -> Relationship? {
|
||||
func relationship(for id: String) -> Relationship? {
|
||||
return relationships[id]
|
||||
}
|
||||
|
||||
static func set(relationship: Relationship, id: String) {
|
||||
func set(relationship: Relationship, id: String) {
|
||||
relationships[id] = relationship
|
||||
}
|
||||
|
||||
static func relationship(for id: String, completion: @escaping (Relationship?) -> Void) {
|
||||
let request = MastodonController.client.getRelationships(accounts: [id])
|
||||
MastodonController.client.run(request) { response in
|
||||
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])
|
||||
mastodonController.run(request) { response in
|
||||
guard case let .success(relationships, _) = response,
|
||||
let relationship = relationships.first else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
set(relationship: relationship, id: relationship.id)
|
||||
self.set(relationship: relationship, id: relationship.id)
|
||||
completion(relationship)
|
||||
}
|
||||
}
|
||||
|
||||
static func add(relationship: Relationship) {
|
||||
func add(relationship: Relationship) {
|
||||
set(relationship: relationship, id: relationship.id)
|
||||
}
|
||||
|
||||
static func addAll(relationships: [Relationship]) {
|
||||
func addAll(relationships: [Relationship]) {
|
||||
relationships.forEach(add)
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
static func notification(for id: String) -> Pachyderm.Notification? {
|
||||
func notification(for id: String) -> Pachyderm.Notification? {
|
||||
return notifications[id]
|
||||
}
|
||||
|
||||
static func set(notification: Pachyderm.Notification, id: String) {
|
||||
func set(notification: Pachyderm.Notification, id: String) {
|
||||
notifications[id] = notification
|
||||
}
|
||||
|
||||
static func add(notification: Pachyderm.Notification) {
|
||||
func add(notification: Pachyderm.Notification) {
|
||||
set(notification: notification, id: notification.id)
|
||||
}
|
||||
|
||||
static func addAll(notifications: [Pachyderm.Notification]) {
|
||||
func addAll(notifications: [Pachyderm.Notification]) {
|
||||
notifications.forEach(add)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// SavedDataManager.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/19/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
class SavedDataManager: Codable {
|
||||
private(set) static var shared: SavedDataManager = load()
|
||||
|
||||
private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
private static var archiveURL = SavedDataManager.documentsDirectory.appendingPathComponent("saved_data").appendingPathExtension("plist")
|
||||
|
||||
static func save() {
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
let encoder = PropertyListEncoder()
|
||||
let data = try? encoder.encode(shared)
|
||||
try? data?.write(to: archiveURL, options: .noFileProtection)
|
||||
}
|
||||
}
|
||||
|
||||
static func load() -> SavedDataManager {
|
||||
let decoder = PropertyListDecoder()
|
||||
if let data = try? Data(contentsOf: archiveURL),
|
||||
let savedHashtagsManager = try? decoder.decode(Self.self, from: data) {
|
||||
return savedHashtagsManager
|
||||
}
|
||||
return SavedDataManager()
|
||||
}
|
||||
|
||||
private init() {}
|
||||
|
||||
private var savedHashtags: [String: [Hashtag]] = [:] {
|
||||
didSet {
|
||||
SavedDataManager.save()
|
||||
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private var savedInstances: [String: [URL]] = [:] {
|
||||
didSet {
|
||||
SavedDataManager.save()
|
||||
NotificationCenter.default.post(name: .savedInstancesChanged, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func sortedHashtags(for account: LocalData.UserAccountInfo) -> [Hashtag] {
|
||||
if let hashtags = savedHashtags[account.id] {
|
||||
return hashtags.sorted(by: { $0.name < $1.name })
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func isSaved(hashtag: Hashtag, for account: LocalData.UserAccountInfo) -> Bool {
|
||||
return savedHashtags[account.id]?.contains(hashtag) ?? false
|
||||
}
|
||||
|
||||
func add(hashtag: Hashtag, for account: LocalData.UserAccountInfo) {
|
||||
if isSaved(hashtag: hashtag, for: account) {
|
||||
return
|
||||
}
|
||||
if var saved = savedHashtags[account.id] {
|
||||
saved.append(hashtag)
|
||||
savedHashtags[account.id] = saved
|
||||
} else {
|
||||
savedHashtags[account.id] = [hashtag]
|
||||
}
|
||||
}
|
||||
|
||||
func remove(hashtag: Hashtag, for account: LocalData.UserAccountInfo) {
|
||||
guard isSaved(hashtag: hashtag, for: account) else { return }
|
||||
if var saved = savedHashtags[account.id] {
|
||||
saved.removeAll(where: { $0.name == hashtag.name })
|
||||
savedHashtags[account.id] = saved
|
||||
}
|
||||
}
|
||||
|
||||
func savedInstances(for account: LocalData.UserAccountInfo) -> [URL] {
|
||||
return savedInstances[account.id] ?? []
|
||||
}
|
||||
|
||||
func isSaved(instance url: URL, for account: LocalData.UserAccountInfo) -> Bool {
|
||||
return savedInstances[account.id]?.contains(url) ?? false
|
||||
}
|
||||
|
||||
func add(instance url: URL, for account: LocalData.UserAccountInfo) {
|
||||
if isSaved(instance: url, for: account) { return }
|
||||
if var saved = savedInstances[account.id] {
|
||||
saved.append(url)
|
||||
savedInstances[account.id] = saved
|
||||
} else {
|
||||
savedInstances[account.id] = [url]
|
||||
}
|
||||
}
|
||||
|
||||
func remove(instance url: URL, for account: LocalData.UserAccountInfo) {
|
||||
guard isSaved(instance: url, for: account) else { return }
|
||||
if var saved = savedInstances[account.id] {
|
||||
saved.removeAll(where: { $0 == url })
|
||||
savedInstances[account.id] = saved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Foundation.Notification.Name {
|
||||
static let savedHashtagsChanged = Notification.Name("savedHashtagsChanged")
|
||||
static let savedInstancesChanged = Notification.Name("savedInstancesChanged")
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// SavedHashtagsManager.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/19/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
class SavedHashtagsManager: Codable {
|
||||
private(set) static var shared: SavedHashtagsManager = load()
|
||||
|
||||
private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
private static var archiveURL = SavedHashtagsManager.documentsDirectory.appendingPathComponent("saved_hashtags").appendingPathExtension("plist")
|
||||
|
||||
static func save() {
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
let encoder = PropertyListEncoder()
|
||||
let data = try? encoder.encode(shared)
|
||||
try? data?.write(to: archiveURL, options: .noFileProtection)
|
||||
}
|
||||
}
|
||||
|
||||
static func load() -> SavedHashtagsManager {
|
||||
let decoder = PropertyListDecoder()
|
||||
if let data = try? Data(contentsOf: archiveURL),
|
||||
let savedHashtagsManager = try? decoder.decode(Self.self, from: data) {
|
||||
return savedHashtagsManager
|
||||
}
|
||||
return SavedHashtagsManager()
|
||||
}
|
||||
|
||||
private init() {}
|
||||
|
||||
private var savedHashtags: [Hashtag] = []
|
||||
var sorted: [Hashtag] {
|
||||
return savedHashtags.sorted(by: { $0.name < $1.name })
|
||||
}
|
||||
|
||||
func isSaved(_ hashtag: Hashtag) -> Bool {
|
||||
return savedHashtags.contains(hashtag)
|
||||
}
|
||||
|
||||
func add(_ hashtag: Hashtag) {
|
||||
if isSaved(hashtag) {
|
||||
return
|
||||
}
|
||||
savedHashtags.append(hashtag)
|
||||
SavedHashtagsManager.save()
|
||||
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
|
||||
}
|
||||
|
||||
func remove(_ hashtag: Hashtag) {
|
||||
guard isSaved(hashtag) else { return }
|
||||
savedHashtags.removeAll(where: { $0.name == hashtag.name })
|
||||
SavedHashtagsManager.save()
|
||||
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension Foundation.Notification.Name {
|
||||
static let savedHashtagsChanged = Notification.Name("savedHashtagsChanged")
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
//
|
||||
// SavedInstancesManager.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/19/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class SavedInstanceManager: Codable {
|
||||
private(set) static var shared: SavedInstanceManager = load()
|
||||
|
||||
private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
private static var archiveURL = SavedInstanceManager.documentsDirectory.appendingPathComponent("saved_instances").appendingPathExtension("plist")
|
||||
|
||||
static func save() {
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
let encoder = PropertyListEncoder()
|
||||
let data = try? encoder.encode(shared)
|
||||
try? data?.write(to: archiveURL, options: .noFileProtection)
|
||||
}
|
||||
}
|
||||
|
||||
static func load() -> SavedInstanceManager {
|
||||
let decoder = PropertyListDecoder()
|
||||
if let data = try? Data(contentsOf: archiveURL),
|
||||
let savedInstanceManager = try? decoder.decode(Self.self, from: data) {
|
||||
return savedInstanceManager
|
||||
}
|
||||
return SavedInstanceManager()
|
||||
}
|
||||
|
||||
private init() {}
|
||||
|
||||
private(set) var savedInstances: [URL] = []
|
||||
|
||||
func isSaved(_ url: URL) -> Bool {
|
||||
return savedInstances.contains(url)
|
||||
}
|
||||
|
||||
func add(_ url: URL) {
|
||||
if isSaved(url) {
|
||||
return
|
||||
}
|
||||
savedInstances.append(url)
|
||||
SavedInstanceManager.save()
|
||||
NotificationCenter.default.post(name: .savedInstancesChanged, object: nil)
|
||||
}
|
||||
|
||||
func remove(_ url: URL) {
|
||||
guard isSaved(url) else { return }
|
||||
savedInstances.removeAll(where: { $0 == url })
|
||||
SavedInstanceManager.save()
|
||||
NotificationCenter.default.post(name: .savedInstancesChanged, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static let savedInstancesChanged = Notification.Name("savedInstancesChanged")
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
//
|
||||
// SceneDelegate.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/6/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
guard let windowScene = scene as? UIWindowScene else { return }
|
||||
|
||||
window = UIWindow(windowScene: windowScene)
|
||||
|
||||
if LocalData.shared.onboardingComplete {
|
||||
if session.mastodonController == nil {
|
||||
let account = LocalData.shared.getMostRecentAccount()!
|
||||
session.mastodonController = MastodonController.getForAccount(account)
|
||||
}
|
||||
|
||||
showAppUI()
|
||||
} else {
|
||||
showOnboardingUI()
|
||||
}
|
||||
|
||||
window!.makeKeyAndVisible()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(themePrefChanged), name: .themePreferenceChanged, object: nil)
|
||||
themePrefChanged()
|
||||
|
||||
if let shortcutItem = connectionOptions.shortcutItem {
|
||||
_ = AppShortcutItem.handle(shortcutItem)
|
||||
}
|
||||
}
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||
if URLContexts.count > 1 {
|
||||
fatalError("Cannot open more than 1 URL")
|
||||
}
|
||||
|
||||
let url = URLContexts.first!.url
|
||||
|
||||
if url.host == "x-callback-url" {
|
||||
_ = XCBManager.handle(url: url)
|
||||
} else if var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
let tabBarController = window!.rootViewController as? MainTabBarViewController,
|
||||
let exploreNavController = tabBarController.getTabController(tab: .explore) as? UINavigationController,
|
||||
let exploreController = exploreNavController.viewControllers.first as? ExploreViewController {
|
||||
|
||||
tabBarController.select(tab: .explore)
|
||||
exploreNavController.popToRootViewController(animated: false)
|
||||
|
||||
exploreController.loadViewIfNeeded()
|
||||
exploreController.searchController.isActive = true
|
||||
|
||||
components.scheme = "https"
|
||||
let query = url.absoluteString
|
||||
exploreController.searchController.searchBar.text = query
|
||||
exploreController.resultsController.performSearch(query: query)
|
||||
}
|
||||
}
|
||||
|
||||
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
|
||||
_ = userActivity.handleResume()
|
||||
}
|
||||
|
||||
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
|
||||
completionHandler(AppShortcutItem.handle(shortcutItem))
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
|
||||
Preferences.save()
|
||||
DraftsManager.save()
|
||||
}
|
||||
|
||||
func activateAccount(_ account: LocalData.UserAccountInfo) {
|
||||
LocalData.shared.setMostRecentAccount(account)
|
||||
window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account)
|
||||
showAppUI()
|
||||
}
|
||||
|
||||
func logoutCurrent() {
|
||||
LocalData.shared.removeAccount(LocalData.shared.getMostRecentAccount()!)
|
||||
if LocalData.shared.onboardingComplete {
|
||||
activateAccount(LocalData.shared.accounts.first!)
|
||||
} else {
|
||||
showOnboardingUI()
|
||||
}
|
||||
}
|
||||
|
||||
func showAppUI() {
|
||||
let mastodonController = window!.windowScene!.session.mastodonController!
|
||||
mastodonController.getOwnAccount()
|
||||
mastodonController.getOwnInstance()
|
||||
|
||||
let tabBarController = MainTabBarViewController(mastodonController: mastodonController)
|
||||
window!.rootViewController = tabBarController
|
||||
}
|
||||
|
||||
func showOnboardingUI() {
|
||||
let onboarding = OnboardingViewController()
|
||||
onboarding.onboardingDelegate = self
|
||||
window!.rootViewController = onboarding
|
||||
}
|
||||
|
||||
@objc func themePrefChanged() {
|
||||
window?.overrideUserInterfaceStyle = Preferences.shared.theme
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SceneDelegate: OnboardingViewControllerDelegate {
|
||||
func didFinishOnboarding(account: LocalData.UserAccountInfo) {
|
||||
activateAccount(account)
|
||||
}
|
||||
}
|
|
@ -12,10 +12,13 @@ class AccountListTableViewController: EnhancedTableViewController {
|
|||
|
||||
private let accountCell = "accountCell"
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
let accountIDs: [String]
|
||||
|
||||
init(accountIDs: [String]) {
|
||||
init(accountIDs: [String], mastodonController: MastodonController) {
|
||||
self.accountIDs = accountIDs
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .grouped)
|
||||
}
|
||||
|
@ -50,12 +53,14 @@ class AccountListTableViewController: EnhancedTableViewController {
|
|||
guard let cell = tableView.dequeueReusableCell(withIdentifier: accountCell, for: indexPath) as? AccountTableViewCell else { fatalError() }
|
||||
|
||||
let id = accountIDs[indexPath.row]
|
||||
cell.updateUI(accountID: id)
|
||||
cell.delegate = self
|
||||
cell.updateUI(accountID: id)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AccountListTableViewController: TuskerNavigationDelegate {}
|
||||
extension AccountListTableViewController: TuskerNavigationDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
|||
|
||||
private let statusCell = "statusCell"
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
var statuses: [(id: String, state: StatusState)] = [] {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
|
@ -24,7 +26,9 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
|||
var newer: RequestRange?
|
||||
var older: RequestRange?
|
||||
|
||||
init() {
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .plain)
|
||||
|
||||
title = NSLocalizedString("Bookmarks", comment: "bookmarks screen title")
|
||||
|
@ -44,10 +48,10 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
|||
|
||||
tableView.prefetchDataSource = self
|
||||
|
||||
let request = MastodonController.client.getBookmarks()
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.getBookmarks()
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||
MastodonCache.addAll(statuses: statuses)
|
||||
self.mastodonController.cache.addAll(statuses: statuses)
|
||||
self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) })
|
||||
self.newer = pagination?.newer
|
||||
self.older = pagination?.older
|
||||
|
@ -81,11 +85,11 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
|||
return
|
||||
}
|
||||
|
||||
let request = MastodonController.client.getBookmarks(range: older)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.getBookmarks(range: older)
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
self.older = pagination?.older
|
||||
MastodonCache.addAll(statuses: newStatuses)
|
||||
self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||
self.statuses.append(contentsOf: newStatuses.map { ($0.id, .unknown) })
|
||||
}
|
||||
}
|
||||
|
@ -101,15 +105,15 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
|||
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
let cellConfig = (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.trailingSwipeActionsConfiguration()
|
||||
|
||||
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else {
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else {
|
||||
return cellConfig
|
||||
}
|
||||
|
||||
let unbookmarkAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Unbookmark", comment: "unbookmark action title")) { (action, view, completion) in
|
||||
let request = Status.unbookmark(status)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case let .success(newStatus, _) = response else { fatalError() }
|
||||
MastodonCache.add(status: newStatus)
|
||||
self.mastodonController.cache.add(status: newStatus)
|
||||
self.statuses.remove(at: indexPath.row)
|
||||
}
|
||||
}
|
||||
|
@ -127,13 +131,13 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
override func getSuggestedContextMenuActions(tableView: UITableView, indexPath: IndexPath, point: CGPoint) -> [UIAction] {
|
||||
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { return [] }
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { 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
|
||||
let request = Status.unbookmark(status)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case let .success(newStatus, _) = response else { fatalError() }
|
||||
MastodonCache.add(status: newStatus)
|
||||
self.mastodonController.cache.add(status: newStatus)
|
||||
self.statuses.remove(at: indexPath.row)
|
||||
}
|
||||
})
|
||||
|
@ -143,6 +147,8 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
extension BookmarksTableViewController: StatusTableViewCellDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
|
||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
|
@ -152,7 +158,7 @@ extension BookmarksTableViewController: StatusTableViewCellDelegate {
|
|||
extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
|
||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments where attachment.kind == .image {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
|
@ -162,7 +168,7 @@ extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
|
|||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
for attachment in status.attachments where attachment.kind == .image {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
|
|
|
@ -12,6 +12,8 @@ import Intents
|
|||
|
||||
class ComposeViewController: UIViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
var inReplyToID: String?
|
||||
var accountsToMention = [String]()
|
||||
var initialText: String?
|
||||
|
@ -70,9 +72,11 @@ class ComposeViewController: UIViewController {
|
|||
|
||||
@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
|
||||
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
|
||||
if let inReplyToID = inReplyToID, let inReplyTo = mastodonController.cache.status(for: inReplyToID) {
|
||||
accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct }
|
||||
} else {
|
||||
accountsToMention = []
|
||||
|
@ -80,7 +84,7 @@ class ComposeViewController: UIViewController {
|
|||
if let mentioningAcct = mentioningAcct {
|
||||
accountsToMention.append(mentioningAcct)
|
||||
}
|
||||
if let ownAccount = MastodonController.account {
|
||||
if let ownAccount = mastodonController.account {
|
||||
accountsToMention.removeAll(where: { acct in ownAccount.acct == acct })
|
||||
}
|
||||
accountsToMention = accountsToMention.uniques()
|
||||
|
@ -127,7 +131,7 @@ class ComposeViewController: UIViewController {
|
|||
statusTextView.text = accountsToMention.map({ acct in "@\(acct) " }).joined()
|
||||
initialText = statusTextView.text
|
||||
|
||||
MastodonController.getOwnAccount { (account) in
|
||||
mastodonController.getOwnAccount { (account) in
|
||||
DispatchQueue.main.async {
|
||||
self.selfDetailView.update(account: account)
|
||||
}
|
||||
|
@ -150,13 +154,13 @@ class ComposeViewController: UIViewController {
|
|||
}
|
||||
|
||||
if let inReplyToID = inReplyToID {
|
||||
if let status = MastodonCache.status(for: inReplyToID) {
|
||||
if let status = mastodonController.cache.status(for: inReplyToID) {
|
||||
updateInReplyTo(inReplyTo: status)
|
||||
} else {
|
||||
let loadingVC = LoadingViewController()
|
||||
embedChild(loadingVC)
|
||||
|
||||
MastodonCache.status(for: inReplyToID) { (status) in
|
||||
mastodonController.cache.status(for: inReplyToID) { (status) in
|
||||
guard let status = status else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.updateInReplyTo(inReplyTo: status)
|
||||
|
@ -189,6 +193,7 @@ class ComposeViewController: UIViewController {
|
|||
}
|
||||
|
||||
let replyView = ComposeStatusReplyView.create()
|
||||
replyView.mastodonController = mastodonController
|
||||
replyView.updateUI(for: inReplyTo)
|
||||
stackView.insertArrangedSubview(replyView, at: 0)
|
||||
|
||||
|
@ -290,7 +295,7 @@ class ComposeViewController: UIViewController {
|
|||
func updateCharactersRemaining() {
|
||||
let count = CharacterCounter.count(text: statusTextView.text)
|
||||
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 {
|
||||
charactersRemainingLabel.textColor = .red
|
||||
compositionState.formUnion(.tooManyCharacters)
|
||||
|
@ -316,7 +321,7 @@ class ComposeViewController: UIViewController {
|
|||
}
|
||||
|
||||
func updateAddAttachmentButton() {
|
||||
switch MastodonController.instance.instanceType {
|
||||
switch mastodonController.instance.instanceType {
|
||||
case .pleroma:
|
||||
addAttachmentButton.isEnabled = true
|
||||
case .mastodon:
|
||||
|
@ -363,10 +368,11 @@ class ComposeViewController: UIViewController {
|
|||
attachments.append(.init(attachment: attachment, description: description))
|
||||
}
|
||||
let cw = contentWarningEnabled ? contentWarningTextField.text : nil
|
||||
let account = mastodonController.accountInfo!
|
||||
if let currentDraft = self.currentDraft {
|
||||
currentDraft.update(text: self.statusTextView.text, contentWarning: cw, attachments: attachments)
|
||||
currentDraft.update(accountID: account.id, text: self.statusTextView.text, contentWarning: cw, attachments: attachments)
|
||||
} else {
|
||||
self.currentDraft = DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, inReplyToID: inReplyToID, attachments: attachments)
|
||||
self.currentDraft = DraftsManager.shared.create(accountID: account.id, text: self.statusTextView.text, contentWarning: cw, inReplyToID: inReplyToID, attachments: attachments)
|
||||
}
|
||||
DraftsManager.save()
|
||||
}
|
||||
|
@ -451,7 +457,7 @@ class ComposeViewController: UIViewController {
|
|||
}
|
||||
|
||||
@objc func draftsButtonPressed() {
|
||||
let draftsVC = DraftsTableViewController()
|
||||
let draftsVC = DraftsTableViewController(account: mastodonController.accountInfo!)
|
||||
draftsVC.delegate = self
|
||||
present(UINavigationController(rootViewController: draftsVC), animated: true)
|
||||
}
|
||||
|
@ -500,8 +506,8 @@ class ComposeViewController: UIViewController {
|
|||
compAttachment.getData { (data, mimeType) in
|
||||
self.postProgressView.step()
|
||||
|
||||
let request = MastodonController.client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case let .success(attachment, _) = response else { fatalError() }
|
||||
|
||||
attachments[index] = attachment
|
||||
|
@ -519,7 +525,7 @@ class ComposeViewController: UIViewController {
|
|||
group.notify(queue: .main) {
|
||||
let attachments = attachments.compactMap { $0 }
|
||||
|
||||
let request = MastodonController.client.createStatus(text: text,
|
||||
let request = Client.createStatus(text: text,
|
||||
contentType: Preferences.shared.statusContentType,
|
||||
inReplyTo: self.inReplyToID,
|
||||
media: attachments,
|
||||
|
@ -527,10 +533,10 @@ class ComposeViewController: UIViewController {
|
|||
spoilerText: contentWarning,
|
||||
visibility: visibility,
|
||||
language: nil)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case let .success(status, _) = response else { fatalError() }
|
||||
self.postedStatus = status
|
||||
MastodonCache.add(status: status)
|
||||
self.mastodonController.cache.add(status: status)
|
||||
|
||||
if let draft = self.currentDraft {
|
||||
DraftsManager.shared.remove(draft)
|
||||
|
@ -540,7 +546,7 @@ class ComposeViewController: UIViewController {
|
|||
self.postProgressView.step()
|
||||
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.xcbSession?.complete(with: .success, additionalData: [
|
||||
|
@ -581,7 +587,7 @@ extension ComposeViewController: UITextViewDelegate {
|
|||
|
||||
extension ComposeViewController: AssetPickerViewControllerDelegate {
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool {
|
||||
switch MastodonController.instance.instanceType {
|
||||
switch mastodonController.instance.instanceType {
|
||||
case .pleroma:
|
||||
return true
|
||||
case .mastodon:
|
||||
|
@ -618,7 +624,6 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
|||
|
||||
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void) {
|
||||
if draft.inReplyToID != self.inReplyToID {
|
||||
// todo: better text for this
|
||||
let alertController = UIAlertController(title: "Different Reply", message: "The selected draft is a reply to a different status, do you wish to use it?", preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
|
||||
completion(false)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
protocol DraftsTableViewControllerDelegate {
|
||||
protocol DraftsTableViewControllerDelegate: class {
|
||||
func draftSelectionCanceled()
|
||||
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void)
|
||||
func draftSelected(_ draft: DraftsManager.Draft)
|
||||
|
@ -17,9 +17,14 @@ protocol DraftsTableViewControllerDelegate {
|
|||
|
||||
class DraftsTableViewController: UITableViewController {
|
||||
|
||||
var delegate: DraftsTableViewControllerDelegate?
|
||||
let account: LocalData.UserAccountInfo
|
||||
weak var delegate: DraftsTableViewControllerDelegate?
|
||||
|
||||
var drafts = [DraftsManager.Draft]()
|
||||
|
||||
init(account: LocalData.UserAccountInfo) {
|
||||
self.account = account
|
||||
|
||||
init() {
|
||||
super.init(nibName: "DraftsTableViewController", bundle: nil)
|
||||
|
||||
title = "Drafts"
|
||||
|
@ -37,10 +42,14 @@ class DraftsTableViewController: UITableViewController {
|
|||
tableView.estimatedRowHeight = 140
|
||||
|
||||
tableView.register(UINib(nibName: "DraftTableViewCell", bundle: nil), forCellReuseIdentifier: "draftCell")
|
||||
|
||||
drafts = DraftsManager.shared.sorted.filter { (draft) in
|
||||
draft.accountID == account.id
|
||||
}
|
||||
}
|
||||
|
||||
func draft(for indexPath: IndexPath) -> DraftsManager.Draft {
|
||||
return DraftsManager.shared.sorted[indexPath.row]
|
||||
return drafts[indexPath.row]
|
||||
}
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
@ -50,7 +59,7 @@ class DraftsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return DraftsManager.shared.drafts.count
|
||||
return drafts.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
|
|
@ -15,6 +15,8 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
static let showPostsImage = UIImage(systemName: "eye.fill")!
|
||||
static let hidePostsImage = UIImage(systemName: "eye.slash.fill")!
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
let mainStatusID: String
|
||||
let mainStatusState: StatusState
|
||||
var statuses: [(id: String, state: StatusState)] = [] {
|
||||
|
@ -28,9 +30,10 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
var showStatusesAutomatically = false
|
||||
var visibilityBarButtonItem: UIBarButtonItem!
|
||||
|
||||
init(for mainStatusID: String, state: StatusState = .unknown) {
|
||||
init(for mainStatusID: String, state: StatusState = .unknown, mastodonController: MastodonController) {
|
||||
self.mainStatusID = mainStatusID
|
||||
self.mainStatusState = state
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .plain)
|
||||
}
|
||||
|
@ -55,14 +58,14 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
|
||||
statuses = [(mainStatusID, mainStatusState)]
|
||||
|
||||
guard let mainStatus = MastodonCache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID)") }
|
||||
guard let mainStatus = mastodonController.cache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID)") }
|
||||
|
||||
let request = Status.getContext(mainStatus)
|
||||
MastodonController.client.run(request) { response in
|
||||
mastodonController.run(request) { response in
|
||||
guard case let .success(context, _) = response else { fatalError() }
|
||||
let parents = self.getDirectParents(of: mainStatus, from: context.ancestors)
|
||||
MastodonCache.addAll(statuses: parents)
|
||||
MastodonCache.addAll(statuses: context.descendants)
|
||||
self.mastodonController.cache.addAll(statuses: parents)
|
||||
self.mastodonController.cache.addAll(statuses: context.descendants)
|
||||
self.statuses = parents.map { ($0.id, .unknown) } + self.statuses + context.descendants.map { ($0.id, .unknown) }
|
||||
let indexPath = IndexPath(row: parents.count, section: 0)
|
||||
DispatchQueue.main.async {
|
||||
|
@ -101,14 +104,14 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainStatusCell", for: indexPath) as? ConversationMainStatusTableViewCell else { fatalError() }
|
||||
cell.selectionStyle = .none
|
||||
cell.showStatusAutomatically = showStatusesAutomatically
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
return cell
|
||||
} else {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
||||
cell.showStatusAutomatically = showStatusesAutomatically
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +158,7 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
extension ConversationTableViewController: StatusTableViewCellDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||
// causes the table view to recalculate the cell heights
|
||||
tableView.beginUpdates()
|
||||
|
@ -165,7 +169,7 @@ extension ConversationTableViewController: StatusTableViewCellDelegate {
|
|||
extension ConversationTableViewController: UITableViewDataSourcePrefetching {
|
||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
|
@ -175,7 +179,7 @@ extension ConversationTableViewController: UITableViewDataSourcePrefetching {
|
|||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
|
|
|
@ -52,7 +52,7 @@ class AddSavedHashtagViewController: SearchResultsViewController {
|
|||
|
||||
extension AddSavedHashtagViewController: SearchResultsViewControllerDelegate {
|
||||
func selectedSearchResult(hashtag: Hashtag) {
|
||||
SavedHashtagsManager.shared.add(hashtag)
|
||||
SavedDataManager.shared.add(hashtag: hashtag, for: mastodonController.accountInfo!)
|
||||
dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,16 @@ import Pachyderm
|
|||
|
||||
class ExploreViewController: EnhancedTableViewController {
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
var dataSource: DataSource!
|
||||
|
||||
var resultsController: SearchResultsViewController!
|
||||
var searchController: UISearchController!
|
||||
|
||||
init() {
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .insetGrouped)
|
||||
|
||||
title = NSLocalizedString("Explore", comment: "explore tab title")
|
||||
|
@ -77,18 +81,20 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
})
|
||||
dataSource.exploreController = self
|
||||
|
||||
let account = mastodonController.accountInfo!
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.bookmarks, .lists, .savedHashtags, .savedInstances])
|
||||
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
||||
snapshot.appendItems([.addList], toSection: .lists)
|
||||
snapshot.appendItems(SavedHashtagsManager.shared.sorted.map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags)
|
||||
snapshot.appendItems(SavedInstanceManager.shared.savedInstances.map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances)
|
||||
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags)
|
||||
snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances)
|
||||
// the initial, static items should not be displayed with an animation
|
||||
UIView.performWithoutAnimation {
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
resultsController = SearchResultsViewController()
|
||||
resultsController = SearchResultsViewController(mastodonController: mastodonController)
|
||||
resultsController.exploreNavigationController = self.navigationController!
|
||||
searchController = UISearchController(searchResultsController: resultsController)
|
||||
searchController.searchResultsUpdater = resultsController
|
||||
|
@ -106,8 +112,8 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
func reloadLists() {
|
||||
let request = MastodonController.client.getLists()
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.getLists()
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(lists, _) = response else {
|
||||
fatalError()
|
||||
}
|
||||
|
@ -123,16 +129,18 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
@objc func savedHashtagsChanged() {
|
||||
let account = mastodonController.accountInfo!
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedHashtags))
|
||||
snapshot.appendItems(SavedHashtagsManager.shared.sorted.map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags)
|
||||
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags)
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
@objc func savedInstancesChanged() {
|
||||
let account = mastodonController.accountInfo!
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedInstances))
|
||||
snapshot.appendItems(SavedInstanceManager.shared.savedInstances.map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances)
|
||||
snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances)
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
|
@ -143,7 +151,7 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
alert.addAction(UIAlertAction(title: NSLocalizedString("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in
|
||||
|
||||
let request = List.delete(list)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case .success(_, _) = response else {
|
||||
fatalError()
|
||||
}
|
||||
|
@ -159,11 +167,13 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
func removeSavedHashtag(_ hashtag: Hashtag) {
|
||||
SavedHashtagsManager.shared.remove(hashtag)
|
||||
let account = mastodonController.accountInfo!
|
||||
SavedDataManager.shared.remove(hashtag: hashtag, for: account)
|
||||
}
|
||||
|
||||
func removeSavedInstance(_ instanceURL: URL) {
|
||||
SavedInstanceManager.shared.remove(instanceURL)
|
||||
let account = mastodonController.accountInfo!
|
||||
SavedDataManager.shared.remove(instance: instanceURL, for: account)
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate
|
||||
|
@ -174,10 +184,10 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
return
|
||||
|
||||
case .bookmarks:
|
||||
show(BookmarksTableViewController(), sender: nil)
|
||||
show(BookmarksTableViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case let .list(list):
|
||||
show(ListTimelineViewController(for: list), sender: nil)
|
||||
show(ListTimelineViewController(for: list, mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .addList:
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
|
@ -189,14 +199,14 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
fatalError()
|
||||
}
|
||||
|
||||
let request = MastodonController.client.createList(title: title)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.createList(title: title)
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case let .success(list, _) = response else { fatalError() }
|
||||
|
||||
self.reloadLists()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let listTimelineController = ListTimelineViewController(for: list)
|
||||
let listTimelineController = ListTimelineViewController(for: list, mastodonController: self.mastodonController)
|
||||
listTimelineController.presentEditOnAppear = true
|
||||
self.show(listTimelineController, sender: nil)
|
||||
}
|
||||
|
@ -205,19 +215,19 @@ class ExploreViewController: EnhancedTableViewController {
|
|||
present(alert, animated: true)
|
||||
|
||||
case let .savedHashtag(hashtag):
|
||||
show(HashtagTimelineViewController(for: hashtag), sender: nil)
|
||||
show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .addSavedHashtag:
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
let navController = UINavigationController(rootViewController: AddSavedHashtagViewController())
|
||||
let navController = UINavigationController(rootViewController: AddSavedHashtagViewController(mastodonController: mastodonController))
|
||||
present(navController, animated: true)
|
||||
|
||||
case let .savedInstance(url):
|
||||
show(InstanceTimelineViewController(for: url), sender: nil)
|
||||
show(InstanceTimelineViewController(for: url, parentMastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .findInstance:
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
let findController = FindInstanceViewController()
|
||||
let findController = FindInstanceViewController(parentMastodonController: mastodonController)
|
||||
findController.instanceTimelineDelegate = self
|
||||
let navController = UINavigationController(rootViewController: findController)
|
||||
present(navController, animated: true)
|
||||
|
@ -344,7 +354,7 @@ extension ExploreViewController {
|
|||
extension ExploreViewController: InstanceTimelineViewControllerDelegate {
|
||||
func didSaveInstance(url: URL) {
|
||||
dismiss(animated: true) {
|
||||
self.show(InstanceTimelineViewController(for: url), sender: nil)
|
||||
self.show(InstanceTimelineViewController(for: url, parentMastodonController: self.mastodonController), sender: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,20 @@ import UIKit
|
|||
|
||||
class FindInstanceViewController: InstanceSelectorTableViewController {
|
||||
|
||||
weak var parentMastodonController: MastodonController?
|
||||
|
||||
var instanceTimelineDelegate: InstanceTimelineViewControllerDelegate?
|
||||
|
||||
init(parentMastodonController: MastodonController) {
|
||||
self.parentMastodonController = parentMastodonController
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
@ -32,7 +44,7 @@ class FindInstanceViewController: InstanceSelectorTableViewController {
|
|||
|
||||
extension FindInstanceViewController: InstanceSelectorTableViewControllerDelegate {
|
||||
func didSelectInstance(url: URL) {
|
||||
let instanceTimelineController = InstanceTimelineViewController(for: url)
|
||||
let instanceTimelineController = InstanceTimelineViewController(for: url, parentMastodonController: parentMastodonController!)
|
||||
instanceTimelineController.delegate = instanceTimelineDelegate
|
||||
show(instanceTimelineController, sender: self)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import Pachyderm
|
|||
|
||||
class EditListAccountsViewController: EnhancedTableViewController {
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
let list: List
|
||||
|
||||
var dataSource: DataSource!
|
||||
|
@ -20,8 +22,9 @@ class EditListAccountsViewController: EnhancedTableViewController {
|
|||
var searchResultsController: SearchResultsViewController!
|
||||
var searchController: UISearchController!
|
||||
|
||||
init(list: List) {
|
||||
init(list: List, mastodonController: MastodonController) {
|
||||
self.list = list
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .plain)
|
||||
|
||||
|
@ -49,7 +52,7 @@ class EditListAccountsViewController: EnhancedTableViewController {
|
|||
})
|
||||
dataSource.editListAccountsController = self
|
||||
|
||||
searchResultsController = SearchResultsViewController()
|
||||
searchResultsController = SearchResultsViewController(mastodonController: mastodonController)
|
||||
searchResultsController.delegate = self
|
||||
searchResultsController.onlySections = [.accounts]
|
||||
searchController = UISearchController(searchResultsController: searchResultsController)
|
||||
|
@ -70,14 +73,14 @@ class EditListAccountsViewController: EnhancedTableViewController {
|
|||
|
||||
func loadAccounts() {
|
||||
let request = List.getAccounts(list)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(accounts, pagination) = response else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
self.nextRange = pagination?.older
|
||||
|
||||
MastodonCache.addAll(accounts: accounts)
|
||||
self.mastodonController.cache.addAll(accounts: accounts)
|
||||
|
||||
var snapshot = self.dataSource.snapshot()
|
||||
snapshot.deleteSections([.accounts])
|
||||
|
@ -109,7 +112,7 @@ class EditListAccountsViewController: EnhancedTableViewController {
|
|||
fatalError()
|
||||
}
|
||||
let request = List.update(self.list, title: text)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
self.mastodonController.run(request) { (response) in
|
||||
guard case .success(_, _) = response else {
|
||||
fatalError()
|
||||
}
|
||||
|
@ -143,7 +146,7 @@ extension EditListAccountsViewController {
|
|||
}
|
||||
|
||||
let request = List.remove(editListAccountsController!.list, accounts: [id])
|
||||
MastodonController.client.run(request) { (response) in
|
||||
editListAccountsController!.mastodonController.run(request) { (response) in
|
||||
guard case .success(_, _) = response else {
|
||||
fatalError()
|
||||
}
|
||||
|
@ -157,7 +160,7 @@ extension EditListAccountsViewController {
|
|||
extension EditListAccountsViewController: SearchResultsViewControllerDelegate {
|
||||
func selectedSearchResult(account accountID: String) {
|
||||
let request = List.add(list, accounts: [accountID])
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case .success(_, _) = response else {
|
||||
fatalError()
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ class ListTimelineViewController: TimelineTableViewController {
|
|||
|
||||
var presentEditOnAppear = false
|
||||
|
||||
init(for list: List) {
|
||||
init(for list: List, mastodonController: MastodonController) {
|
||||
self.list = list
|
||||
|
||||
super.init(for: .list(id: list.id))
|
||||
super.init(for: .list(id: list.id), mastodonController: mastodonController)
|
||||
|
||||
title = list.title
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class ListTimelineViewController: TimelineTableViewController {
|
|||
}
|
||||
|
||||
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))
|
||||
let navController = UINavigationController(rootViewController: editListAccountsController)
|
||||
present(navController, animated: animated)
|
||||
|
|
|
@ -10,6 +10,8 @@ import UIKit
|
|||
|
||||
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
return .portrait
|
||||
|
@ -18,17 +20,27 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.delegate = self
|
||||
|
||||
viewControllers = [
|
||||
embedInNavigationController(TimelinesPageViewController()),
|
||||
embedInNavigationController(NotificationsPageViewController()),
|
||||
ComposeViewController(),
|
||||
embedInNavigationController(ExploreViewController()),
|
||||
embedInNavigationController(MyProfileTableViewController()),
|
||||
embedInNavigationController(TimelinesPageViewController(mastodonController: mastodonController)),
|
||||
embedInNavigationController(NotificationsPageViewController(mastodonController: mastodonController)),
|
||||
ComposeViewController(mastodonController: mastodonController),
|
||||
embedInNavigationController(ExploreViewController(mastodonController: mastodonController)),
|
||||
embedInNavigationController(MyProfileTableViewController(mastodonController: mastodonController)),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -49,7 +61,7 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
|||
}
|
||||
|
||||
func presentCompose() {
|
||||
let compose = ComposeViewController()
|
||||
let compose = ComposeViewController(mastodonController: mastodonController)
|
||||
let navigationController = embedInNavigationController(compose)
|
||||
navigationController.presentationController?.delegate = compose
|
||||
present(navigationController, animated: true)
|
||||
|
|
|
@ -14,12 +14,16 @@ class NotificationsPageViewController: SegmentedPageViewController {
|
|||
private let notificationsTitle = NSLocalizedString("Notifications", comment: "notifications tab title")
|
||||
private let mentionsTitle = NSLocalizedString("Mentions", comment: "mentions tab title")
|
||||
|
||||
init() {
|
||||
let notifications = NotificationsTableViewController(allowedTypes: Pachyderm.Notification.Kind.allCases)
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
let notifications = NotificationsTableViewController(allowedTypes: Pachyderm.Notification.Kind.allCases, mastodonController: mastodonController)
|
||||
notifications.title = notificationsTitle
|
||||
notifications.userActivity = UserActivityManager.checkNotificationsActivity()
|
||||
|
||||
let mentions = NotificationsTableViewController(allowedTypes: [.mention])
|
||||
let mentions = NotificationsTableViewController(allowedTypes: [.mention], mastodonController: mastodonController)
|
||||
mentions.title = mentionsTitle
|
||||
mentions.userActivity = UserActivityManager.checkMentionsActivity()
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
private let followGroupCell = "followGroupCell"
|
||||
private let followRequestCell = "followRequestCell"
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
let excludedTypes: [Pachyderm.Notification.Kind]
|
||||
let groupTypes = [Notification.Kind.favourite, .reblog, .follow]
|
||||
|
||||
|
@ -30,8 +32,9 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
var newer: 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.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .plain)
|
||||
|
||||
|
@ -56,17 +59,17 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
|
||||
tableView.prefetchDataSource = self
|
||||
|
||||
let request = MastodonController.client.getNotifications(excludeTypes: excludedTypes)
|
||||
MastodonController.client.run(request) { result in
|
||||
let request = Client.getNotifications(excludeTypes: excludedTypes)
|
||||
mastodonController.run(request) { result in
|
||||
guard case let .success(notifications, pagination) = result else { fatalError() }
|
||||
|
||||
let groups = NotificationGroup.createGroups(notifications: notifications, only: self.groupTypes)
|
||||
|
||||
self.groups.append(contentsOf: groups)
|
||||
|
||||
MastodonCache.addAll(notifications: notifications)
|
||||
MastodonCache.addAll(statuses: notifications.compactMap { $0.status })
|
||||
MastodonCache.addAll(accounts: notifications.map { $0.account })
|
||||
self.mastodonController.cache.addAll(notifications: notifications)
|
||||
self.mastodonController.cache.addAll(statuses: notifications.compactMap { $0.status })
|
||||
self.mastodonController.cache.addAll(accounts: notifications.map { $0.account })
|
||||
|
||||
self.newer = pagination?.newer
|
||||
self.older = pagination?.older
|
||||
|
@ -89,31 +92,31 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
|
||||
switch group.kind {
|
||||
case .mention:
|
||||
guard let notification = MastodonCache.notification(for: group.notificationIDs.first!),
|
||||
guard let notification = mastodonController.cache.notification(for: group.notificationIDs.first!),
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else {
|
||||
fatalError()
|
||||
}
|
||||
cell.updateUI(statusID: notification.status!.id, state: group.statusState!)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: notification.status!.id, state: group.statusState!)
|
||||
return cell
|
||||
|
||||
case .favourite, .reblog:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: actionGroupCell, for: indexPath) as? ActionNotificationGroupTableViewCell else { fatalError() }
|
||||
cell.updateUI(group: group)
|
||||
cell.delegate = self
|
||||
cell.updateUI(group: group)
|
||||
return cell
|
||||
|
||||
case .follow:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: followGroupCell, for: indexPath) as? FollowNotificationGroupTableViewCell else { fatalError() }
|
||||
cell.updateUI(group: group)
|
||||
cell.delegate = self
|
||||
cell.updateUI(group: group)
|
||||
return cell
|
||||
|
||||
case .followRequest:
|
||||
guard let notification = MastodonCache.notification(for: group.notificationIDs.first!),
|
||||
guard let notification = mastodonController.cache.notification(for: group.notificationIDs.first!),
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: followRequestCell, for: indexPath) as? FollowRequestNotificationTableViewCell else { fatalError() }
|
||||
cell.updateUI(notification: notification)
|
||||
cell.delegate = self
|
||||
cell.updateUI(notification: notification)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
@ -124,17 +127,17 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
if indexPath.row == groups.count - 1 {
|
||||
guard let older = older else { return }
|
||||
|
||||
let request = MastodonController.client.getNotifications(excludeTypes: excludedTypes, range: older)
|
||||
MastodonController.client.run(request) { result in
|
||||
let request = Client.getNotifications(excludeTypes: excludedTypes, range: older)
|
||||
mastodonController.run(request) { result in
|
||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||
|
||||
let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes)
|
||||
|
||||
self.groups.append(contentsOf: groups)
|
||||
|
||||
MastodonCache.addAll(notifications: newNotifications)
|
||||
MastodonCache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||
MastodonCache.addAll(accounts: newNotifications.map { $0.account })
|
||||
self.mastodonController.cache.addAll(notifications: newNotifications)
|
||||
self.mastodonController.cache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||
self.mastodonController.cache.addAll(accounts: newNotifications.map { $0.account })
|
||||
|
||||
self.older = pagination?.older
|
||||
}
|
||||
|
@ -182,7 +185,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
.map(Pachyderm.Notification.dismiss(id:))
|
||||
.forEach { (request) in
|
||||
group.enter()
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
@ -196,17 +199,17 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
@objc func refreshNotifications(_ sender: Any) {
|
||||
guard let newer = newer else { return }
|
||||
|
||||
let request = MastodonController.client.getNotifications(excludeTypes: excludedTypes, range: newer)
|
||||
MastodonController.client.run(request) { result in
|
||||
let request = Client.getNotifications(excludeTypes: excludedTypes, range: newer)
|
||||
mastodonController.run(request) { result in
|
||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||
|
||||
let groups = NotificationGroup.createGroups(notifications: newNotifications, only: self.groupTypes)
|
||||
|
||||
self.groups.insert(contentsOf: groups, at: 0)
|
||||
|
||||
MastodonCache.addAll(notifications: newNotifications)
|
||||
MastodonCache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||
MastodonCache.addAll(accounts: newNotifications.map { $0.account })
|
||||
self.mastodonController.cache.addAll(notifications: newNotifications)
|
||||
self.mastodonController.cache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||
self.mastodonController.cache.addAll(accounts: newNotifications.map { $0.account })
|
||||
|
||||
if let newer = pagination?.newer {
|
||||
self.newer = newer
|
||||
|
@ -224,6 +227,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
extension NotificationsTableViewController: StatusTableViewCellDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||
// causes the table view to recalculate the cell heights
|
||||
tableView.beginUpdates()
|
||||
|
@ -235,7 +239,7 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
for notificationID in groups[indexPath.row].notificationIDs {
|
||||
guard let notification = MastodonCache.notification(for: notificationID) else { continue }
|
||||
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.get(notification.account.avatar, completion: nil)
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +248,7 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
for notificationID in groups[indexPath.row].notificationIDs {
|
||||
guard let notification = MastodonCache.notification(for: notificationID) else { continue }
|
||||
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue }
|
||||
ImageCache.avatars.cancel(notification.account.avatar)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import Combine
|
||||
import Pachyderm
|
||||
|
||||
protocol InstanceSelectorTableViewControllerDelegate {
|
||||
protocol InstanceSelectorTableViewControllerDelegate: class {
|
||||
func didSelectInstance(url: URL)
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ fileprivate let instanceCell = "instanceCell"
|
|||
|
||||
class InstanceSelectorTableViewController: UITableViewController {
|
||||
|
||||
var delegate: InstanceSelectorTableViewControllerDelegate?
|
||||
weak var delegate: InstanceSelectorTableViewControllerDelegate?
|
||||
|
||||
var dataSource: DataSource!
|
||||
var searchController: UISearchController!
|
||||
|
@ -115,7 +115,7 @@ class InstanceSelectorTableViewController: UITableViewController {
|
|||
let components = parseURLComponents(input: domain)
|
||||
|
||||
let client = Client(baseURL: components.url!)
|
||||
let request = client.getInstance()
|
||||
let request = Client.getInstance()
|
||||
client.run(request) { (response) in
|
||||
var snapshot = self.dataSource.snapshot()
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .selected))
|
||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import AuthenticationServices
|
||||
|
||||
protocol OnboardingViewControllerDelegate {
|
||||
func didFinishOnboarding()
|
||||
func didFinishOnboarding(account: LocalData.UserAccountInfo)
|
||||
}
|
||||
|
||||
class OnboardingViewController: UINavigationController {
|
||||
|
@ -44,15 +44,13 @@ class OnboardingViewController: UINavigationController {
|
|||
}
|
||||
|
||||
extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate {
|
||||
func didSelectInstance(url: URL) {
|
||||
LocalData.shared.instanceURL = url
|
||||
MastodonController.createClient()
|
||||
MastodonController.registerApp {
|
||||
let clientID = LocalData.shared.clientID!
|
||||
func didSelectInstance(url instanceURL: URL) {
|
||||
let mastodonController = MastodonController(instanceURL: instanceURL)
|
||||
mastodonController.registerApp { (clientID, clientSecret) in
|
||||
|
||||
let callbackURL = "tusker://oauth"
|
||||
|
||||
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
|
||||
var components = URLComponents(url: instanceURL, resolvingAgainstBaseURL: false)!
|
||||
components.path = "/oauth/authorize"
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "client_id", value: clientID),
|
||||
|
@ -69,9 +67,13 @@ extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate
|
|||
let item = components.queryItems?.first(where: { $0.name == "code" }),
|
||||
let authCode = item.value else { return }
|
||||
|
||||
MastodonController.authorize(authorizationCode: authCode) {
|
||||
mastodonController.authorize(authorizationCode: authCode) { (accessToken) in
|
||||
mastodonController.getOwnAccount { (account) in
|
||||
DispatchQueue.main.async {
|
||||
self.onboardingDelegate?.didFinishOnboarding()
|
||||
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
|
||||
|
||||
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,11 @@ import SwiftUI
|
|||
|
||||
class PreferencesNavigationController: UINavigationController {
|
||||
|
||||
init() {
|
||||
let hostingController = UIHostingController(rootView: PreferencesView())
|
||||
private var isSwitchingAccounts = false
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
let view = PreferencesView()
|
||||
let hostingController = UIHostingController(rootView: view)
|
||||
super.init(rootViewController: hostingController)
|
||||
hostingController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
|
||||
}
|
||||
|
@ -21,15 +24,66 @@ class PreferencesNavigationController: UINavigationController {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
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(userLoggedOut), name: .userLoggedOut, object: nil)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
if !isSwitchingAccounts {
|
||||
// workaround for onDisappear not being called when a modally presented UIHostingController is dismissed
|
||||
NotificationCenter.default.post(name: .preferencesChanged, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func donePressed() {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
@objc func showAddAccount() {
|
||||
let onboardingController = OnboardingViewController()
|
||||
onboardingController.onboardingDelegate = self
|
||||
onboardingController.instanceSelector.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelAddAccount))
|
||||
show(onboardingController, sender: self)
|
||||
}
|
||||
|
||||
@objc func cancelAddAccount() {
|
||||
dismiss(animated: true) // dismisses instance selector
|
||||
}
|
||||
|
||||
@objc func activateAccount(_ notification: Notification) {
|
||||
let account = notification.userInfo!["account"] as! LocalData.UserAccountInfo
|
||||
let sceneDelegate = self.view.window!.windowScene!.delegate as! SceneDelegate
|
||||
isSwitchingAccounts = true
|
||||
dismiss(animated: true) { // dismiss preferences
|
||||
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 PreferencesNavigationController: OnboardingViewControllerDelegate {
|
||||
func didFinishOnboarding(account: LocalData.UserAccountInfo) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct PreferencesView : View {
|
||||
struct PreferencesView: View {
|
||||
@ObservedObject var localData = LocalData.shared
|
||||
@State private var showingLogoutConfirmation = false
|
||||
|
||||
var body: some View {
|
||||
|
@ -15,14 +16,37 @@ struct PreferencesView : View {
|
|||
// NavigationView {
|
||||
List {
|
||||
Section {
|
||||
ForEach(localData.accounts, id: \.accessToken) { (account) in
|
||||
Button(action: {
|
||||
NotificationCenter.default.post(name: .activateAccount, object: nil, userInfo: ["account": account])
|
||||
}) {
|
||||
HStack {
|
||||
Text(account.username)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
if account == self.localData.getMostRecentAccount() {
|
||||
Image(systemName: "checkmark")
|
||||
.renderingMode(.template)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
NotificationCenter.default.post(name: .addAccount, object: nil)
|
||||
}) {
|
||||
Text("Add Account...")
|
||||
}
|
||||
if localData.getMostRecentAccount() != nil {
|
||||
Button(action: {
|
||||
self.showingLogoutConfirmation = true
|
||||
}) {
|
||||
Text("Logout")
|
||||
Text("Logout from current")
|
||||
}.alert(isPresented: $showingLogoutConfirmation) {
|
||||
Alert(title: Text("Are you sure you want to logout?"), message: nil, primaryButton: .destructive(Text("Logout"), action: self.logoutPressed), secondaryButton: .cancel())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
NavigationLink(destination: AppearancePrefsView()) {
|
||||
|
@ -49,11 +73,6 @@ struct PreferencesView : View {
|
|||
}
|
||||
|
||||
func logoutPressed() {
|
||||
LocalData.shared.onboardingComplete = false
|
||||
LocalData.shared.instanceURL = nil
|
||||
LocalData.shared.clientID = nil
|
||||
LocalData.shared.clientSecret = nil
|
||||
LocalData.shared.accessToken = nil
|
||||
NotificationCenter.default.post(name: .userLoggedOut, object: nil)
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +80,7 @@ struct PreferencesView : View {
|
|||
#if DEBUG
|
||||
struct PreferencesView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
PreferencesView()
|
||||
return PreferencesView()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -11,14 +11,14 @@ import SwiftUI
|
|||
|
||||
class MyProfileTableViewController: ProfileTableViewController {
|
||||
|
||||
init() {
|
||||
super.init(accountID: nil)
|
||||
init(mastodonController: MastodonController) {
|
||||
super.init(accountID: nil, mastodonController: mastodonController)
|
||||
|
||||
title = "My Profile"
|
||||
tabBarItem.image = UIImage(systemName: "person.fill")
|
||||
|
||||
|
||||
MastodonController.getOwnAccount { (account) in
|
||||
mastodonController.getOwnAccount { (account) in
|
||||
self.accountID = account.id
|
||||
|
||||
ImageCache.avatars.get(account.avatar, completion: { (data) in
|
||||
|
@ -50,7 +50,7 @@ class MyProfileTableViewController: ProfileTableViewController {
|
|||
}
|
||||
|
||||
@objc func preferencesPressed() {
|
||||
present(PreferencesNavigationController(), animated: true)
|
||||
present(PreferencesNavigationController(mastodonController: mastodonController), animated: true)
|
||||
}
|
||||
|
||||
@objc func closePreferences() {
|
||||
|
|
|
@ -12,6 +12,8 @@ import SafariServices
|
|||
|
||||
class ProfileTableViewController: EnhancedTableViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
var accountID: String! {
|
||||
didSet {
|
||||
if shouldLoadOnAccountIDSet {
|
||||
|
@ -43,7 +45,9 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
var shouldLoadOnAccountIDSet = false
|
||||
var loadingVC: LoadingViewController? = nil
|
||||
|
||||
init(accountID: String?) {
|
||||
init(accountID: String?, mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
self.accountID = accountID
|
||||
|
||||
super.init(style: .plain)
|
||||
|
@ -69,12 +73,12 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
tableView.prefetchDataSource = self
|
||||
|
||||
if let accountID = accountID {
|
||||
if MastodonCache.account(for: accountID) != nil {
|
||||
if mastodonController.cache.account(for: accountID) != nil {
|
||||
updateAccountUI()
|
||||
} else {
|
||||
loadingVC = LoadingViewController()
|
||||
embedChild(loadingVC!)
|
||||
MastodonCache.account(for: accountID) { (account) in
|
||||
mastodonController.cache.account(for: accountID) { (account) in
|
||||
guard account != nil else {
|
||||
let alert = UIAlertController(title: "Something Went Wrong", message: "Couldn't load the selected account", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
|
||||
|
@ -108,14 +112,14 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
getStatuses(onlyPinned: true) { (response) in
|
||||
guard case let .success(statuses, _) = response else { fatalError() }
|
||||
|
||||
MastodonCache.addAll(statuses: statuses)
|
||||
self.mastodonController.cache.addAll(statuses: statuses)
|
||||
self.pinnedStatuses = statuses.map { ($0.id, .unknown) }
|
||||
}
|
||||
|
||||
getStatuses() { response in
|
||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||
|
||||
MastodonCache.addAll(statuses: statuses)
|
||||
self.mastodonController.cache.addAll(statuses: statuses)
|
||||
self.timelineSegments.append(statuses.map { ($0.id, .unknown) })
|
||||
|
||||
self.older = pagination?.older
|
||||
|
@ -124,18 +128,18 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") }
|
||||
guard let accountID = accountID, let account = mastodonController.cache.account(for: accountID) else { return }
|
||||
navigationItem.title = account.realDisplayName
|
||||
}
|
||||
|
||||
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)
|
||||
MastodonController.client.run(request, completion: completion)
|
||||
mastodonController.run(request, completion: completion)
|
||||
}
|
||||
|
||||
func sendMessageMentioning() {
|
||||
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") }
|
||||
let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct))
|
||||
guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") }
|
||||
let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, mastodonController: mastodonController))
|
||||
present(vc, animated: true)
|
||||
}
|
||||
|
||||
|
@ -148,7 +152,7 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if section == 0 {
|
||||
return accountID == nil || MastodonCache.account(for: accountID) == nil ? 0 : 1
|
||||
return accountID == nil || mastodonController.cache.account(for: accountID) == nil ? 0 : 1
|
||||
} else if section == 1 {
|
||||
return pinnedStatuses.count
|
||||
} else {
|
||||
|
@ -168,14 +172,14 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
||||
let (id, state) = pinnedStatuses[indexPath.row]
|
||||
cell.showPinned = true
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
return cell
|
||||
default:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
||||
let (id, state) = timelineSegments[indexPath.section - 2][indexPath.row]
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +193,7 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
getStatuses(for: older) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
|
||||
MastodonCache.addAll(statuses: newStatuses)
|
||||
self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||
self.timelineSegments[indexPath.section - 2].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
|
||||
|
||||
self.older = pagination?.older
|
||||
|
@ -215,7 +219,7 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
getStatuses(for: newer) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
|
||||
MastodonCache.addAll(statuses: newStatuses)
|
||||
self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
|
||||
|
||||
if let newer = pagination?.newer {
|
||||
|
@ -229,7 +233,7 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
|
||||
getStatuses(onlyPinned: true) { (response) in
|
||||
guard case let .success(newPinnedStatuses, _) = response else { fatalError() }
|
||||
MastodonCache.addAll(statuses: newPinnedStatuses)
|
||||
self.mastodonController.cache.addAll(statuses: newPinnedStatuses)
|
||||
|
||||
let oldPinnedStatuses = self.pinnedStatuses
|
||||
var pinnedStatuses = [(id: String, state: StatusState)]()
|
||||
|
@ -253,6 +257,8 @@ class ProfileTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
extension ProfileTableViewController: StatusTableViewCellDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
|
||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||
// causes the table view to recalculate the cell heights
|
||||
tableView.beginUpdates()
|
||||
|
@ -262,9 +268,9 @@ extension ProfileTableViewController: StatusTableViewCellDelegate {
|
|||
|
||||
extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
|
||||
func showMoreOptions(cell: ProfileHeaderTableViewCell) {
|
||||
let account = MastodonCache.account(for: accountID)!
|
||||
let account = mastodonController.cache.account(for: accountID)!
|
||||
|
||||
MastodonCache.relationship(for: account.id) { [weak self] (relationship) in
|
||||
mastodonController.cache.relationship(for: account.id) { [weak self] (relationship) in
|
||||
guard let self = self else { return }
|
||||
|
||||
var customActivities: [UIActivity] = [OpenInSafariActivity()]
|
||||
|
@ -287,7 +293,7 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths where indexPath.section > 1 {
|
||||
let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id
|
||||
guard let status = MastodonCache.status(for: statusID) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
|
@ -298,7 +304,7 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching {
|
|||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths where indexPath.section > 1 {
|
||||
let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id
|
||||
guard let status = MastodonCache.status(for: statusID) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
|
|
|
@ -28,6 +28,8 @@ extension SearchResultsViewControllerDelegate {
|
|||
|
||||
class SearchResultsViewController: EnhancedTableViewController {
|
||||
|
||||
let mastodonController: MastodonController!
|
||||
|
||||
weak var exploreNavigationController: UINavigationController?
|
||||
weak var delegate: SearchResultsViewControllerDelegate?
|
||||
|
||||
|
@ -40,7 +42,9 @@ class SearchResultsViewController: EnhancedTableViewController {
|
|||
let searchSubject = PassthroughSubject<String?, Never>()
|
||||
var currentQuery: String?
|
||||
|
||||
init() {
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .grouped)
|
||||
|
||||
title = NSLocalizedString("Search", comment: "search screen title")
|
||||
|
@ -61,18 +65,18 @@ class SearchResultsViewController: EnhancedTableViewController {
|
|||
switch item {
|
||||
case let .account(id):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: accountCell, for: indexPath) as! AccountTableViewCell
|
||||
cell.updateUI(accountID: id)
|
||||
cell.delegate = self
|
||||
cell.updateUI(accountID: id)
|
||||
return cell
|
||||
case let .hashtag(tag):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: hashtagCell, for: indexPath) as! HashtagTableViewCell
|
||||
cell.updateUI(hashtag: tag)
|
||||
cell.delegate = self
|
||||
cell.updateUI(hashtag: tag)
|
||||
return cell
|
||||
case let .status(id, state):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as! TimelineStatusTableViewCell
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
return cell
|
||||
}
|
||||
})
|
||||
|
@ -117,8 +121,8 @@ class SearchResultsViewController: EnhancedTableViewController {
|
|||
activityIndicator.startAnimating()
|
||||
}
|
||||
|
||||
let request = MastodonController.client.search(query: query, resolve: true, limit: 10)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.search(query: query, resolve: true, limit: 10)
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(results, _) = response else { fatalError() }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
@ -132,7 +136,7 @@ class SearchResultsViewController: EnhancedTableViewController {
|
|||
if self.onlySections.contains(.accounts) && !results.accounts.isEmpty {
|
||||
snapshot.appendSections([.accounts])
|
||||
snapshot.appendItems(results.accounts.map { .account($0.id) }, toSection: .accounts)
|
||||
MastodonCache.addAll(accounts: results.accounts)
|
||||
self.mastodonController.cache.addAll(accounts: results.accounts)
|
||||
}
|
||||
if self.onlySections.contains(.hashtags) && !results.hashtags.isEmpty {
|
||||
snapshot.appendSections([.hashtags])
|
||||
|
@ -141,8 +145,8 @@ class SearchResultsViewController: EnhancedTableViewController {
|
|||
if self.onlySections.contains(.statuses) && !results.statuses.isEmpty {
|
||||
snapshot.appendSections([.statuses])
|
||||
snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses)
|
||||
MastodonCache.addAll(statuses: results.statuses)
|
||||
MastodonCache.addAll(accounts: results.statuses.map { $0.account })
|
||||
self.mastodonController.cache.addAll(statuses: results.statuses)
|
||||
self.mastodonController.cache.addAll(accounts: results.statuses.map { $0.account })
|
||||
}
|
||||
self.dataSource.apply(snapshot)
|
||||
}
|
||||
|
@ -217,6 +221,7 @@ extension SearchResultsViewController: UISearchBarDelegate {
|
|||
}
|
||||
|
||||
extension SearchResultsViewController: StatusTableViewCellDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
|
|
|
@ -14,6 +14,8 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
|||
private let statusCell = "statusCell"
|
||||
private let accountCell = "accountCell"
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
let actionType: ActionType
|
||||
let statusID: String
|
||||
var statusState: StatusState
|
||||
|
@ -32,8 +34,11 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
|||
- Parameter actionType The action that this VC is for.
|
||||
- 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 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.statusID = statusID
|
||||
self.statusState = statusState
|
||||
|
@ -68,16 +73,16 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
|||
|
||||
if accountIDs == nil {
|
||||
// account IDs haven't been set, so perform a request to load them
|
||||
guard let status = MastodonCache.status(for: statusID) else {
|
||||
guard let status = mastodonController.cache.status(for: statusID) else {
|
||||
fatalError("Missing cached status \(statusID)")
|
||||
}
|
||||
|
||||
tableView.tableFooterView = UIActivityIndicatorView(style: .large)
|
||||
|
||||
let request = actionType == .favorite ? Status.getFavourites(status) : Status.getReblogs(status)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(accounts, _) = response else { fatalError() }
|
||||
MastodonCache.addAll(accounts: accounts)
|
||||
self.mastodonController.cache.addAll(accounts: accounts)
|
||||
DispatchQueue.main.async {
|
||||
self.accountIDs = accounts.map { $0.id }
|
||||
self.tableView.tableFooterView = nil
|
||||
|
@ -111,14 +116,14 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
|||
switch indexPath.section {
|
||||
case 0:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
||||
cell.updateUI(statusID: statusID, state: statusState)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: statusID, state: statusState)
|
||||
return cell
|
||||
case 1:
|
||||
guard let accountIDs = accountIDs,
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: accountCell, for: indexPath) as? AccountTableViewCell else { fatalError() }
|
||||
cell.updateUI(accountID: accountIDs[indexPath.row])
|
||||
cell.delegate = self
|
||||
cell.updateUI(accountID: accountIDs[indexPath.row])
|
||||
return cell
|
||||
default:
|
||||
fatalError("Invalid section \(indexPath.section)")
|
||||
|
@ -137,6 +142,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||
// causes the table view to recalculate the cell heights
|
||||
tableView.beginUpdates()
|
||||
|
|
|
@ -15,17 +15,17 @@ class HashtagTimelineViewController: TimelineTableViewController {
|
|||
|
||||
var toggleSaveButton: UIBarButtonItem!
|
||||
var toggleSaveButtonTitle: String {
|
||||
if SavedHashtagsManager.shared.isSaved(hashtag) {
|
||||
if SavedDataManager.shared.isSaved(hashtag: hashtag, for: mastodonController.accountInfo!) {
|
||||
return NSLocalizedString("Unsave", comment: "unsave hashtag button")
|
||||
} else {
|
||||
return NSLocalizedString("Save", comment: "save hashtag button")
|
||||
}
|
||||
}
|
||||
|
||||
init(for hashtag: Hashtag) {
|
||||
init(for hashtag: Hashtag, mastodonController: MastodonController) {
|
||||
self.hashtag = hashtag
|
||||
|
||||
super.init(for: .tag(hashtag: hashtag.name))
|
||||
super.init(for: .tag(hashtag: hashtag.name), mastodonController: mastodonController)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -48,10 +48,10 @@ class HashtagTimelineViewController: TimelineTableViewController {
|
|||
// MARK: - Interaction
|
||||
|
||||
@objc func toggleSaveButtonPressed() {
|
||||
if SavedHashtagsManager.shared.isSaved(hashtag) {
|
||||
SavedHashtagsManager.shared.remove(hashtag)
|
||||
if SavedDataManager.shared.isSaved(hashtag: hashtag, for: mastodonController.accountInfo!) {
|
||||
SavedDataManager.shared.remove(hashtag: hashtag, for: mastodonController.accountInfo!)
|
||||
} else {
|
||||
SavedHashtagsManager.shared.add(hashtag)
|
||||
SavedDataManager.shared.add(hashtag: hashtag, for: mastodonController.accountInfo!)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,30 +8,38 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
protocol InstanceTimelineViewControllerDelegate {
|
||||
protocol InstanceTimelineViewControllerDelegate: class {
|
||||
func didSaveInstance(url: URL)
|
||||
func didUnsaveInstance(url: URL)
|
||||
}
|
||||
|
||||
class InstanceTimelineViewController: TimelineTableViewController {
|
||||
|
||||
var delegate: InstanceTimelineViewControllerDelegate?
|
||||
weak var delegate: InstanceTimelineViewControllerDelegate?
|
||||
|
||||
weak var parentMastodonController: MastodonController?
|
||||
|
||||
let instanceURL: URL
|
||||
let instanceMastodonController: MastodonController
|
||||
|
||||
var toggleSaveButton: UIBarButtonItem!
|
||||
var toggleSaveButtonTitle: String {
|
||||
if SavedInstanceManager.shared.isSaved(instanceURL) {
|
||||
if SavedDataManager.shared.isSaved(instance: instanceURL, for: parentMastodonController!.accountInfo!) {
|
||||
return NSLocalizedString("Unsave", comment: "unsave instance button")
|
||||
} else {
|
||||
return NSLocalizedString("Save", comment: "save instance button")
|
||||
}
|
||||
}
|
||||
|
||||
init(for url: URL) {
|
||||
init(for url: URL, parentMastodonController: MastodonController) {
|
||||
self.parentMastodonController = parentMastodonController
|
||||
|
||||
self.instanceURL = url
|
||||
|
||||
super.init(for: .instance(instanceURL: url))
|
||||
// the timeline VC only stores a weak reference to it, so we need to store a strong reference to make sure it's not released immediately
|
||||
instanceMastodonController = MastodonController(instanceURL: url)
|
||||
|
||||
super.init(for: .instance(instanceURL: url), mastodonController: instanceMastodonController)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -51,6 +59,15 @@ class InstanceTimelineViewController: TimelineTableViewController {
|
|||
toggleSaveButton.title = toggleSaveButtonTitle
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath) as! TimelineStatusTableViewCell
|
||||
cell.delegate = nil
|
||||
cell.overrideMastodonController = mastodonController
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
@ -59,11 +76,11 @@ class InstanceTimelineViewController: TimelineTableViewController {
|
|||
|
||||
// MARK: - Interaction
|
||||
@objc func toggleSaveButtonPressed() {
|
||||
if SavedInstanceManager.shared.isSaved(instanceURL) {
|
||||
SavedInstanceManager.shared.remove(instanceURL)
|
||||
if SavedDataManager.shared.isSaved(instance: instanceURL, for: parentMastodonController!.accountInfo!) {
|
||||
SavedDataManager.shared.remove(instance: instanceURL, for: parentMastodonController!.accountInfo!)
|
||||
delegate?.didUnsaveInstance(url: instanceURL)
|
||||
} else {
|
||||
SavedInstanceManager.shared.add(instanceURL)
|
||||
SavedDataManager.shared.add(instance: instanceURL, for: parentMastodonController!.accountInfo!)
|
||||
delegate?.didSaveInstance(url: instanceURL)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import Pachyderm
|
|||
class TimelineTableViewController: EnhancedTableViewController {
|
||||
|
||||
var timeline: Timeline!
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
var timelineSegments: [[(id: String, state: StatusState)]] = [] {
|
||||
didSet {
|
||||
|
@ -24,8 +25,9 @@ class TimelineTableViewController: EnhancedTableViewController {
|
|||
var newer: RequestRange?
|
||||
var older: RequestRange?
|
||||
|
||||
init(for timeline: Timeline) {
|
||||
init(for timeline: Timeline, mastodonController: MastodonController) {
|
||||
self.timeline = timeline
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .plain)
|
||||
|
||||
|
@ -56,15 +58,14 @@ class TimelineTableViewController: EnhancedTableViewController {
|
|||
|
||||
tableView.prefetchDataSource = self
|
||||
|
||||
guard MastodonController.client?.accessToken != nil else { return }
|
||||
loadInitialStatuses()
|
||||
}
|
||||
|
||||
func loadInitialStatuses() {
|
||||
let request = MastodonController.client.getStatuses(timeline: timeline)
|
||||
MastodonController.client.run(request) { response in
|
||||
let request = Client.getStatuses(timeline: timeline)
|
||||
mastodonController.run(request) { response in
|
||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||
MastodonCache.addAll(statuses: statuses)
|
||||
self.mastodonController.cache.addAll(statuses: statuses)
|
||||
self.timelineSegments.insert(statuses.map { ($0.id, .unknown) }, at: 0)
|
||||
self.newer = pagination?.newer
|
||||
self.older = pagination?.older
|
||||
|
@ -86,8 +87,8 @@ class TimelineTableViewController: EnhancedTableViewController {
|
|||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
||||
|
||||
let (id, state) = timelineSegments[indexPath.section][indexPath.row]
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: id, state: state)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
@ -99,11 +100,11 @@ class TimelineTableViewController: EnhancedTableViewController {
|
|||
indexPath.row == timelineSegments[indexPath.section].count - 1 {
|
||||
guard let older = older else { return }
|
||||
|
||||
let request = MastodonController.client.getStatuses(timeline: timeline, range: older)
|
||||
MastodonController.client.run(request) { response in
|
||||
let request = Client.getStatuses(timeline: timeline, range: older)
|
||||
mastodonController.run(request) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
self.older = pagination?.older
|
||||
MastodonCache.addAll(statuses: newStatuses)
|
||||
self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||
self.timelineSegments[self.timelineSegments.count - 1].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
|
||||
}
|
||||
}
|
||||
|
@ -124,11 +125,11 @@ class TimelineTableViewController: EnhancedTableViewController {
|
|||
@objc func refreshStatuses(_ sender: Any) {
|
||||
guard let newer = newer else { return }
|
||||
|
||||
let request = MastodonController.client.getStatuses(timeline: timeline, range: newer)
|
||||
MastodonController.client.run(request) { response in
|
||||
let request = Client.getStatuses(timeline: timeline, range: newer)
|
||||
mastodonController.run(request) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
|
||||
MastodonCache.addAll(statuses: newStatuses)
|
||||
self.newer = pagination?.newer
|
||||
self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
|
||||
|
||||
if let newer = pagination?.newer {
|
||||
|
@ -151,6 +152,8 @@ class TimelineTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
|
||||
extension TimelineTableViewController: StatusTableViewCellDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
|
||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||
// causes the table view to recalculate the cell heights
|
||||
tableView.beginUpdates()
|
||||
|
@ -161,7 +164,7 @@ extension TimelineTableViewController: StatusTableViewCellDelegate {
|
|||
extension TimelineTableViewController: UITableViewDataSourcePrefetching {
|
||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = MastodonCache.status(for: statusID(for: indexPath)) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statusID(for: indexPath)) else { continue }
|
||||
ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.get(attachment.url, completion: nil)
|
||||
|
@ -171,7 +174,7 @@ extension TimelineTableViewController: UITableViewDataSourcePrefetching {
|
|||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||
for indexPath in indexPaths {
|
||||
guard let status = MastodonCache.status(for: statusID(for: indexPath)) else { continue }
|
||||
guard let status = mastodonController.cache.status(for: statusID(for: indexPath)) else { continue }
|
||||
ImageCache.avatars.cancel(status.account.avatar)
|
||||
for attachment in status.attachments {
|
||||
ImageCache.attachments.cancel(attachment.url)
|
||||
|
|
|
@ -14,14 +14,18 @@ class TimelinesPageViewController: SegmentedPageViewController {
|
|||
private let federatedTitle = NSLocalizedString("Federated", comment: "federated timeline tab title")
|
||||
private let localTitle = NSLocalizedString("Local", comment: "local timeline tab title")
|
||||
|
||||
init() {
|
||||
let home = TimelineTableViewController(for: .home)
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
let home = TimelineTableViewController(for: .home, mastodonController: mastodonController)
|
||||
home.title = homeTitle
|
||||
|
||||
let federated = TimelineTableViewController(for: .public(local: false))
|
||||
let federated = TimelineTableViewController(for: .public(local: false), mastodonController: mastodonController)
|
||||
federated.title = federatedTitle
|
||||
|
||||
let local = TimelineTableViewController(for: .public(local: true))
|
||||
let local = TimelineTableViewController(for: .public(local: true), mastodonController: mastodonController)
|
||||
local.title = localTitle
|
||||
|
||||
super.init(titles: [
|
||||
|
|
|
@ -22,8 +22,11 @@ protocol MenuPreviewProvider {
|
|||
|
||||
extension MenuPreviewProvider {
|
||||
|
||||
private var mastodonController: MastodonController? { navigationDelegate?.apiController }
|
||||
|
||||
func actionsForProfile(accountID: String, sourceView: UIView?) -> [UIAction] {
|
||||
guard let account = MastodonCache.account(for: accountID) else { return [] }
|
||||
guard let mastodonController = mastodonController,
|
||||
let account = mastodonController.cache.account(for: accountID) else { return [] }
|
||||
return [
|
||||
createAction(identifier: "openinsafari", title: "Open in Safari", systemImageName: "safari", handler: { (_) in
|
||||
self.navigationDelegate?.selected(url: account.url)
|
||||
|
@ -53,7 +56,8 @@ extension MenuPreviewProvider {
|
|||
}
|
||||
|
||||
func actionsForStatus(statusID: String, sourceView: UIView?) -> [UIAction] {
|
||||
guard let status = MastodonCache.status(for: statusID) else { return [] }
|
||||
guard let mastodonController = mastodonController,
|
||||
let status = mastodonController.cache.status(for: statusID) else { return [] }
|
||||
return [
|
||||
createAction(identifier: "reply", title: "Reply", systemImageName: "arrowshape.turn.up.left", handler: { (_) in
|
||||
self.navigationDelegate?.reply(to: statusID)
|
||||
|
|
|
@ -45,7 +45,9 @@ enum AppShortcutItem: String, CaseIterable {
|
|||
case .composePost:
|
||||
tab = .compose
|
||||
}
|
||||
let controller = (UIApplication.shared.delegate!.window!!.rootViewController as! MainTabBarViewController)
|
||||
let scene = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first!
|
||||
let window = scene.windows.first { $0.isKeyWindow }!
|
||||
let controller = window.rootViewController as! MainTabBarViewController
|
||||
controller.select(tab: tab)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,16 @@ class UserActivityManager {
|
|||
private static let encoder = PropertyListEncoder()
|
||||
private static let decoder = PropertyListDecoder()
|
||||
|
||||
private static var mastodonController: MastodonController {
|
||||
let scene = UIApplication.shared.activeOrBackgroundScene!
|
||||
return scene.session.mastodonController!
|
||||
}
|
||||
|
||||
private static func getMainTabBarController() -> MainTabBarViewController {
|
||||
return (UIApplication.shared.delegate! as! AppDelegate).window!.rootViewController as! MainTabBarViewController
|
||||
let scene = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first!
|
||||
let window = scene.windows.first { $0.isKeyWindow }!
|
||||
return window.rootViewController as! MainTabBarViewController
|
||||
// return (UIApplication.shared.delegate! as! AppDelegate).window!.rootViewController as! MainTabBarViewController
|
||||
}
|
||||
|
||||
private static func present(_ vc: UIViewController, animated: Bool = true) {
|
||||
|
@ -42,7 +50,8 @@ class UserActivityManager {
|
|||
static func handleNewPost(activity: NSUserActivity) {
|
||||
// TODO: check not currently showing compose screen
|
||||
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
|
||||
|
@ -60,6 +69,7 @@ class UserActivityManager {
|
|||
if let navigationController = tabBarController.getTabController(tab: .notifications) as? UINavigationController,
|
||||
let notificationsPageController = navigationController.viewControllers.first as? NotificationsPageViewController {
|
||||
navigationController.popToRootViewController(animated: false)
|
||||
notificationsPageController.loadViewIfNeeded()
|
||||
notificationsPageController.selectMode(.allNotifications)
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +89,7 @@ class UserActivityManager {
|
|||
if let navController = tabBarController.getTabController(tab: .notifications) as? UINavigationController,
|
||||
let notificationsPageController = navController.viewControllers.first as? NotificationsPageViewController {
|
||||
navController.popToRootViewController(animated: false)
|
||||
notificationsPageController.loadViewIfNeeded()
|
||||
notificationsPageController.selectMode(.mentionsOnly)
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +155,8 @@ class UserActivityManager {
|
|||
rootController.segmentedControl.selectedSegmentIndex = index
|
||||
rootController.selectPage(at: index, animated: false)
|
||||
default:
|
||||
navigationController.pushViewController(TimelineTableViewController(for: timeline), animated: false)
|
||||
let timeline = TimelineTableViewController(for: timeline, mastodonController: mastodonController)
|
||||
navigationController.pushViewController(timeline, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +194,7 @@ class UserActivityManager {
|
|||
tabBarController.select(tab: .explore)
|
||||
if let navigationController = tabBarController.getTabController(tab: .explore) as? UINavigationController {
|
||||
navigationController.popToRootViewController(animated: false)
|
||||
navigationController.pushViewController(BookmarksTableViewController(), animated: false)
|
||||
navigationController.pushViewController(BookmarksTableViewController(mastodonController: mastodonController), animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ import UIKit
|
|||
import SafariServices
|
||||
import Pachyderm
|
||||
|
||||
protocol TuskerNavigationDelegate {
|
||||
protocol TuskerNavigationDelegate: class {
|
||||
|
||||
var apiController: MastodonController { get }
|
||||
|
||||
func show(_ vc: UIViewController)
|
||||
|
||||
|
@ -74,15 +76,15 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
return
|
||||
}
|
||||
|
||||
show(ProfileTableViewController(accountID: accountID), sender: self)
|
||||
show(ProfileTableViewController(accountID: accountID, mastodonController: apiController), sender: self)
|
||||
}
|
||||
|
||||
func selected(mention: Mention) {
|
||||
show(ProfileTableViewController(accountID: mention.id), sender: self)
|
||||
show(ProfileTableViewController(accountID: mention.id, mastodonController: apiController), sender: self)
|
||||
}
|
||||
|
||||
func selected(tag: Hashtag) {
|
||||
show(HashtagTimelineViewController(for: tag), sender: self)
|
||||
show(HashtagTimelineViewController(for: tag, mastodonController: apiController), sender: self)
|
||||
}
|
||||
|
||||
func selected(url: URL) {
|
||||
|
@ -119,7 +121,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
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
|
||||
|
@ -128,7 +130,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
}
|
||||
|
||||
func compose(mentioning: String?) {
|
||||
let compose = ComposeViewController(mentioningAcct: mentioning)
|
||||
let compose = ComposeViewController(mentioningAcct: mentioning, mastodonController: apiController)
|
||||
let vc = UINavigationController(rootViewController: compose)
|
||||
vc.presentationController?.delegate = compose
|
||||
present(vc, animated: true)
|
||||
|
@ -139,7 +141,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
}
|
||||
|
||||
func reply(to statusID: String, mentioningAcct: String?) {
|
||||
let compose = ComposeViewController(inReplyTo: statusID, mentioningAcct: mentioningAcct)
|
||||
let compose = ComposeViewController(inReplyTo: statusID, mentioningAcct: mentioningAcct, mastodonController: apiController)
|
||||
let vc = UINavigationController(rootViewController: compose)
|
||||
vc.presentationController?.delegate = compose
|
||||
present(vc, animated: true)
|
||||
|
@ -202,7 +204,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
}
|
||||
|
||||
private func moreOptions(forStatus statusID: String) -> UIActivityViewController {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||
guard let status = apiController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||
guard let url = status.url else { fatalError("Missing url for status \(statusID)") }
|
||||
var customActivites: [UIActivity] = [OpenInSafariActivity()]
|
||||
|
||||
|
@ -210,7 +212,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
|
||||
}
|
||||
|
||||
if status.account == MastodonController.account,
|
||||
if status.account == apiController.account,
|
||||
let pinned = status.pinned {
|
||||
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
|
||||
}
|
||||
|
@ -221,7 +223,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
}
|
||||
|
||||
private func moreOptions(forAccount accountID: String) -> UIActivityViewController {
|
||||
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
||||
guard let account = apiController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
||||
return moreOptions(forURL: account.url)
|
||||
}
|
||||
|
||||
|
@ -244,13 +246,13 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
}
|
||||
|
||||
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")
|
||||
show(vc, sender: self)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ import UIKit
|
|||
|
||||
class AccountTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: TuskerNavigationDelegate?
|
||||
weak var delegate: TuskerNavigationDelegate?
|
||||
var mastodonController: MastodonController! { delegate?.apiController }
|
||||
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
|
@ -31,7 +32,7 @@ class AccountTableViewCell: UITableViewCell {
|
|||
@objc func updateUIForPrefrences() {
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
|
||||
guard let account = MastodonCache.account(for: accountID) else {
|
||||
guard let account = mastodonController.cache.account(for: accountID) else {
|
||||
fatalError("Missing cached account \(accountID!)")
|
||||
}
|
||||
displayNameLabel.text = account.realDisplayName
|
||||
|
@ -39,7 +40,7 @@ class AccountTableViewCell: UITableViewCell {
|
|||
|
||||
func updateUI(accountID: String) {
|
||||
self.accountID = accountID
|
||||
guard let account = MastodonCache.account(for: accountID) else {
|
||||
guard let account = mastodonController.cache.account(for: accountID) else {
|
||||
fatalError("Missing cached account \(accountID)")
|
||||
}
|
||||
|
||||
|
@ -68,8 +69,9 @@ extension AccountTableViewCell: MenuPreviewProvider {
|
|||
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
guard let mastodonController = mastodonController else { return nil }
|
||||
return (
|
||||
content: { ProfileTableViewController(accountID: self.accountID) },
|
||||
content: { ProfileTableViewController(accountID: self.accountID, mastodonController: mastodonController) },
|
||||
actions: { self.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ import Pachyderm
|
|||
import Gifu
|
||||
import AVFoundation
|
||||
|
||||
protocol AttachmentViewDelegate {
|
||||
protocol AttachmentViewDelegate: class {
|
||||
func showAttachmentsGallery(startingAt index: Int)
|
||||
}
|
||||
|
||||
class AttachmentView: UIImageView, GIFAnimatable {
|
||||
|
||||
var delegate: AttachmentViewDelegate?
|
||||
weak var delegate: AttachmentViewDelegate?
|
||||
|
||||
var playImageView: UIImageView!
|
||||
|
||||
|
@ -71,8 +71,8 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
}
|
||||
|
||||
func loadImage() {
|
||||
ImageCache.attachments.get(attachment.url) { (data) in
|
||||
guard let data = data else { return }
|
||||
ImageCache.attachments.get(attachment.url) { [weak self] (data) in
|
||||
guard let self = self, let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
if self.attachment.url.pathExtension == "gif" {
|
||||
self.animate(withGIFData: data)
|
||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
|||
|
||||
class AttachmentsContainerView: UIView {
|
||||
|
||||
var delegate: AttachmentViewDelegate?
|
||||
weak var delegate: AttachmentViewDelegate?
|
||||
|
||||
var statusID: String!
|
||||
var attachments: [Attachment]!
|
||||
|
@ -37,8 +37,6 @@ class AttachmentsContainerView: UIView {
|
|||
|
||||
createBlurView()
|
||||
createHideButton()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
func getAttachmentView(for attachment: Attachment) -> AttachmentView? {
|
||||
|
@ -176,11 +174,7 @@ class AttachmentsContainerView: UIView {
|
|||
self.isHidden = true
|
||||
}
|
||||
|
||||
updateUIForPreferences()
|
||||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
contentHidden = Preferences.shared.blurAllMedia || (MastodonCache.status(for: statusID)?.sensitive ?? false)
|
||||
contentHidden = Preferences.shared.blurAllMedia || status.sensitive
|
||||
}
|
||||
|
||||
private func createAttachmentView(index: Int) -> AttachmentView {
|
||||
|
|
|
@ -10,14 +10,14 @@ import UIKit
|
|||
import Photos
|
||||
import AVFoundation
|
||||
|
||||
protocol ComposeMediaViewDelegate {
|
||||
protocol ComposeMediaViewDelegate: class {
|
||||
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
||||
func descriptionTextViewDidChange(_ mediaView: ComposeMediaView)
|
||||
}
|
||||
|
||||
class ComposeMediaView: UIView {
|
||||
|
||||
var delegate: ComposeMediaViewDelegate?
|
||||
weak var delegate: ComposeMediaViewDelegate?
|
||||
|
||||
@IBOutlet weak var imageView: UIImageView!
|
||||
@IBOutlet weak var descriptionTextView: UITextView!
|
||||
|
|
|
@ -11,6 +11,8 @@ import Pachyderm
|
|||
|
||||
class ComposeStatusReplyView: UIView {
|
||||
|
||||
weak var mastodonController: MastodonController?
|
||||
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
|
@ -34,6 +36,7 @@ class ComposeStatusReplyView: UIView {
|
|||
func updateUI(for status: Status) {
|
||||
displayNameLabel.text = status.account.realDisplayName
|
||||
usernameLabel.text = "@\(status.account.acct)"
|
||||
statusContentTextView.overrideMastodonController = mastodonController
|
||||
statusContentTextView.statusID = status.id
|
||||
|
||||
ImageCache.avatars.get(status.account.avatar) { (data) in
|
||||
|
|
|
@ -15,8 +15,9 @@ private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options:
|
|||
|
||||
class ContentTextView: LinkTextView {
|
||||
|
||||
// todo: should be weak
|
||||
var navigationDelegate: TuskerNavigationDelegate?
|
||||
weak var navigationDelegate: TuskerNavigationDelegate?
|
||||
weak var overrideMastodonController: MastodonController?
|
||||
var mastodonController: MastodonController? { overrideMastodonController ?? navigationDelegate?.apiController }
|
||||
|
||||
var defaultFont: UIFont = .systemFont(ofSize: 17)
|
||||
var defaultColor: UIColor = .label
|
||||
|
@ -230,9 +231,9 @@ class ContentTextView: LinkTextView {
|
|||
let text = (self.text as NSString).substring(with: range)
|
||||
|
||||
if let mention = getMention(for: url, text: text) {
|
||||
return ProfileTableViewController(accountID: mention.id)
|
||||
return ProfileTableViewController(accountID: mention.id, mastodonController: mastodonController!)
|
||||
} else if let tag = getHashtag(for: url, text: text) {
|
||||
return HashtagTimelineViewController(for: tag)
|
||||
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController!)
|
||||
} else {
|
||||
return SFSafariViewController(url: url)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
|||
|
||||
class HashtagTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: TuskerNavigationDelegate?
|
||||
weak var delegate: TuskerNavigationDelegate?
|
||||
|
||||
@IBOutlet weak var hashtagLabel: UILabel!
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ import SwiftSoup
|
|||
|
||||
class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: TuskerNavigationDelegate?
|
||||
weak var delegate: TuskerNavigationDelegate?
|
||||
var mastodonController: MastodonController! { delegate?.apiController }
|
||||
|
||||
@IBOutlet weak var actionImageView: UIImageView!
|
||||
@IBOutlet weak var actionAvatarStackView: UIStackView!
|
||||
|
@ -26,6 +27,10 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
var authorAvatarURL: URL?
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
deinit {
|
||||
updateTimestampWorkItem?.cancel()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
|
@ -33,7 +38,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||
updateActionLabel(people: people)
|
||||
|
||||
for case let imageView as UIImageView in actionAvatarStackView.arrangedSubviews {
|
||||
|
@ -47,7 +52,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
}
|
||||
self.group = group
|
||||
|
||||
guard let firstNotification = MastodonCache.notification(for: group.notificationIDs.first!) else { fatalError() }
|
||||
guard let firstNotification = mastodonController.cache.notification(for: group.notificationIDs.first!) else { fatalError() }
|
||||
let status = firstNotification.status!
|
||||
self.statusID = status.id
|
||||
|
||||
|
@ -62,7 +67,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
fatalError()
|
||||
}
|
||||
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||
|
||||
actionAvatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
for account in people {
|
||||
|
@ -93,7 +98,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
|
||||
func updateTimestamp() {
|
||||
guard let id = group.notificationIDs.first,
|
||||
let notification = MastodonCache.notification(for: id) else {
|
||||
let notification = mastodonController.cache.notification(for: id) else {
|
||||
fatalError("Missing cached notification")
|
||||
}
|
||||
|
||||
|
@ -109,7 +114,9 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem(block: self.updateTimestamp)
|
||||
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
} else {
|
||||
updateTimestampWorkItem = nil
|
||||
|
@ -155,7 +162,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
|
||||
func didSelectCell() {
|
||||
guard let delegate = delegate else { return }
|
||||
let notifications = group.notificationIDs.compactMap(MastodonCache.notification(for:))
|
||||
let notifications = group.notificationIDs.compactMap(mastodonController.cache.notification(for:))
|
||||
let accountIDs = notifications.map { $0.account.id }
|
||||
let action: StatusActionAccountListTableViewController.ActionType
|
||||
switch notifications.first!.kind {
|
||||
|
@ -176,7 +183,7 @@ extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
|
|||
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
return (content: {
|
||||
let notifications = self.group.notificationIDs.compactMap(MastodonCache.notification(for:))
|
||||
let notifications = self.group.notificationIDs.compactMap(self.mastodonController.cache.notification(for:))
|
||||
let accountIDs = notifications.map { $0.account.id }
|
||||
let action: StatusActionAccountListTableViewController.ActionType
|
||||
switch notifications.first!.kind {
|
||||
|
|
|
@ -11,7 +11,8 @@ import Pachyderm
|
|||
|
||||
class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: TuskerNavigationDelegate?
|
||||
weak var delegate: TuskerNavigationDelegate?
|
||||
var mastodonController: MastodonController! { delegate?.apiController }
|
||||
|
||||
@IBOutlet weak var avatarStackView: UIStackView!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
|
@ -21,6 +22,10 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
deinit {
|
||||
updateTimestampWorkItem?.cancel()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
|
@ -28,7 +33,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||
updateActionLabel(people: people)
|
||||
|
||||
for case let imageView as UIImageView in avatarStackView.arrangedSubviews {
|
||||
|
@ -39,7 +44,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
func updateUI(group: NotificationGroup) {
|
||||
self.group = group
|
||||
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account }
|
||||
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||
|
||||
updateActionLabel(people: people)
|
||||
updateTimestamp()
|
||||
|
@ -81,7 +86,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
|
||||
func updateTimestamp() {
|
||||
guard let id = group.notificationIDs.first,
|
||||
let notification = MastodonCache.notification(for: id) else {
|
||||
let notification = mastodonController.cache.notification(for: id) else {
|
||||
fatalError("Missing cached notification")
|
||||
}
|
||||
|
||||
|
@ -97,7 +102,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem {
|
||||
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
|
@ -117,7 +122,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
|
||||
extension FollowNotificationGroupTableViewCell: SelectableTableViewCell {
|
||||
func didSelectCell() {
|
||||
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account.id }
|
||||
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account.id }
|
||||
switch people.count {
|
||||
case 0:
|
||||
return
|
||||
|
@ -133,12 +138,13 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
|
|||
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
guard let mastodonController = mastodonController else { return nil }
|
||||
return (content: {
|
||||
let accountIDs = self.group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account.id }
|
||||
let accountIDs = self.group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account.id }
|
||||
if accountIDs.count == 1 {
|
||||
return ProfileTableViewController(accountID: accountIDs.first!)
|
||||
return ProfileTableViewController(accountID: accountIDs.first!, mastodonController: mastodonController)
|
||||
} else {
|
||||
return AccountListTableViewController(accountIDs: accountIDs)
|
||||
return AccountListTableViewController(accountIDs: accountIDs, mastodonController: mastodonController)
|
||||
}
|
||||
}, actions: {
|
||||
return []
|
||||
|
|
|
@ -11,7 +11,8 @@ import Pachyderm
|
|||
|
||||
class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: TuskerNavigationDelegate?
|
||||
weak var delegate: TuskerNavigationDelegate?
|
||||
var mastodonController: MastodonController! { delegate?.apiController }
|
||||
|
||||
@IBOutlet weak var stackView: UIStackView!
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
|
@ -26,6 +27,10 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
deinit {
|
||||
updateTimestampWorkItem?.cancel()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
|
@ -71,7 +76,9 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem(block: self.updateTimestamp)
|
||||
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
} else {
|
||||
updateTimestampWorkItem = nil
|
||||
|
@ -89,9 +96,9 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
|
||||
@IBAction func rejectButtonPressed() {
|
||||
let request = Account.rejectFollowRequest(account)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(relationship, _) = response else { fatalError() }
|
||||
MastodonCache.add(relationship: relationship)
|
||||
self.mastodonController.cache.add(relationship: relationship)
|
||||
DispatchQueue.main.async {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
self.actionButtonsStackView.isHidden = true
|
||||
|
@ -106,9 +113,9 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
|
||||
@IBAction func acceptButtonPressed() {
|
||||
let request = Account.authorizeFollowRequest(account)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
guard case let .success(relationship, _) = response else { fatalError() }
|
||||
MastodonCache.add(relationship: relationship)
|
||||
self.mastodonController.cache.add(relationship: relationship)
|
||||
DispatchQueue.main.async {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
self.actionButtonsStackView.isHidden = true
|
||||
|
@ -133,8 +140,9 @@ extension FollowRequestNotificationTableViewCell: MenuPreviewProvider {
|
|||
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
guard let mastodonController = mastodonController else { return nil }
|
||||
return (content: {
|
||||
return ProfileTableViewController(accountID: self.account.id)
|
||||
return ProfileTableViewController(accountID: self.account.id, mastodonController: mastodonController)
|
||||
}, actions: {
|
||||
return []
|
||||
})
|
||||
|
|
|
@ -15,7 +15,8 @@ protocol ProfileHeaderTableViewCellDelegate: TuskerNavigationDelegate {
|
|||
|
||||
class ProfileHeaderTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: ProfileHeaderTableViewCellDelegate?
|
||||
weak var delegate: ProfileHeaderTableViewCellDelegate?
|
||||
var mastodonController: MastodonController! { delegate?.apiController }
|
||||
|
||||
@IBOutlet weak var headerImageView: UIImageView!
|
||||
@IBOutlet weak var avatarContainerView: UIView!
|
||||
|
@ -55,7 +56,7 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
guard accountID != self.accountID else { return }
|
||||
self.accountID = accountID
|
||||
|
||||
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
||||
guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
||||
|
||||
updateUIForPreferences()
|
||||
|
||||
|
@ -82,12 +83,12 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
noteTextView.setTextFromHtml(account.note)
|
||||
noteTextView.setEmojis(account.emojis)
|
||||
|
||||
if accountID != MastodonController.account.id {
|
||||
if accountID != mastodonController.account.id {
|
||||
// don't show relationship label for the user's own account
|
||||
if let relationship = MastodonCache.relationship(for: accountID) {
|
||||
if let relationship = mastodonController.cache.relationship(for: accountID) {
|
||||
followsYouLabel.isHidden = !relationship.followedBy
|
||||
} else {
|
||||
MastodonCache.relationship(for: accountID) { relationship in
|
||||
mastodonController.cache.relationship(for: accountID) { relationship in
|
||||
DispatchQueue.main.async {
|
||||
self.followsYouLabel.isHidden = !(relationship?.followedBy ?? false)
|
||||
}
|
||||
|
@ -122,7 +123,7 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") }
|
||||
guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") }
|
||||
|
||||
avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView)
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
|
|
|
@ -15,11 +15,14 @@ protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
|
|||
}
|
||||
|
||||
class BaseStatusTableViewCell: UITableViewCell {
|
||||
var delegate: StatusTableViewCellDelegate? {
|
||||
|
||||
weak var delegate: StatusTableViewCellDelegate? {
|
||||
didSet {
|
||||
contentTextView.navigationDelegate = delegate
|
||||
}
|
||||
}
|
||||
var overrideMastodonController: MastodonController?
|
||||
var mastodonController: MastodonController! { overrideMastodonController ?? delegate?.apiController }
|
||||
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
|
@ -92,20 +95,28 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
attachmentsView.isAccessibilityElement = true
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
statusUpdater = MastodonCache.statusSubject
|
||||
.filter { $0.id == self.statusID }
|
||||
open func createObserversIfNecessary() {
|
||||
if statusUpdater == nil {
|
||||
statusUpdater = mastodonController.cache.statusSubject
|
||||
.filter { [unowned self] in $0.id == self.statusID }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: updateStatusState(status:))
|
||||
.sink { [unowned self] in self.updateStatusState(status: $0) }
|
||||
}
|
||||
|
||||
accountUpdater = MastodonCache.accountSubject
|
||||
.filter { $0.id == self.accountID }
|
||||
if accountUpdater == nil {
|
||||
accountUpdater = mastodonController.cache.accountSubject
|
||||
.filter { [unowned self] in $0.id == self.accountID }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: updateUI(account:))
|
||||
.sink { [unowned self] in self.updateUI(account: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
func updateUI(statusID: String, state: StatusState) {
|
||||
guard let status = MastodonCache.status(for: statusID) else {
|
||||
createObserversIfNecessary()
|
||||
|
||||
guard let status = mastodonController.cache.status(for: statusID) else {
|
||||
fatalError("Missing cached status")
|
||||
}
|
||||
self.statusID = statusID
|
||||
|
@ -180,9 +191,10 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
guard let account = MastodonCache.account(for: accountID) else { return }
|
||||
guard let account = mastodonController.cache.account(for: accountID) else { return }
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
displayNameLabel.text = account.realDisplayName
|
||||
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.cache.status(for: statusID)?.sensitive ?? false)
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
|
@ -240,18 +252,18 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@IBAction func favoritePressed() {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
|
||||
let oldValue = favorited
|
||||
favorited = !favorited
|
||||
|
||||
let realStatus: Status = status.reblog ?? status
|
||||
let request = (favorited ? Status.favourite : Status.unfavourite)(realStatus)
|
||||
MastodonController.client.run(request) { response in
|
||||
mastodonController.run(request) { response in
|
||||
DispatchQueue.main.async {
|
||||
if case let .success(newStatus, _) = response {
|
||||
self.favorited = newStatus.favourited ?? false
|
||||
MastodonCache.add(status: newStatus)
|
||||
self.mastodonController.cache.add(status: newStatus)
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
} else {
|
||||
self.favorited = oldValue
|
||||
|
@ -265,18 +277,18 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@IBAction func reblogPressed() {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
|
||||
let oldValue = reblogged
|
||||
reblogged = !reblogged
|
||||
|
||||
let realStatus: Status = status.reblog ?? status
|
||||
let request = (reblogged ? Status.reblog : Status.unreblog)(realStatus)
|
||||
MastodonController.client.run(request) { response in
|
||||
mastodonController.run(request) { response in
|
||||
DispatchQueue.main.async {
|
||||
if case let .success(newStatus, _) = response {
|
||||
self.reblogged = newStatus.reblogged ?? false
|
||||
MastodonCache.add(status: newStatus)
|
||||
self.mastodonController.cache.add(status: newStatus)
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
} else {
|
||||
self.reblogged = oldValue
|
||||
|
@ -303,7 +315,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
|
||||
extension BaseStatusTableViewCell: AttachmentViewDelegate {
|
||||
func showAttachmentsGallery(startingAt index: Int) {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:))
|
||||
delegate?.showGallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index)
|
||||
}
|
||||
|
@ -313,9 +325,10 @@ extension BaseStatusTableViewCell: MenuPreviewProvider {
|
|||
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
guard let mastodonController = mastodonController else { return nil }
|
||||
if avatarImageView.frame.contains(location) {
|
||||
return (
|
||||
content: { ProfileTableViewController(accountID: self.accountID)},
|
||||
content: { ProfileTableViewController(accountID: self.accountID, mastodonController: mastodonController) },
|
||||
actions: { self.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) }
|
||||
)
|
||||
} else if attachmentsView.frame.contains(location) {
|
||||
|
|
|
@ -40,7 +40,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
|||
|
||||
override func updateUI(statusID: String, state: StatusState) {
|
||||
super.updateUI(statusID: statusID, state: state)
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError() }
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError() }
|
||||
|
||||
var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt)
|
||||
if let application = status.application {
|
||||
|
|
|
@ -34,6 +34,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
|
||||
deinit {
|
||||
rebloggerAccountUpdater?.cancel()
|
||||
updateTimestampWorkItem?.cancel()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
|
@ -41,19 +42,25 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
reblogLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reblogLabelPressed)))
|
||||
|
||||
accessibilityElements!.insert(reblogLabel!, at: 0)
|
||||
}
|
||||
|
||||
rebloggerAccountUpdater = MastodonCache.accountSubject
|
||||
.filter { $0.id == self.rebloggerID }
|
||||
override func createObserversIfNecessary() {
|
||||
super.createObserversIfNecessary()
|
||||
|
||||
if rebloggerAccountUpdater == nil {
|
||||
rebloggerAccountUpdater = mastodonController.cache.accountSubject
|
||||
.filter { [unowned self] in $0.id == self.rebloggerID }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: updateRebloggerLabel(reblogger:))
|
||||
.sink { [unowned self] in self.updateRebloggerLabel(reblogger: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
override func updateUI(statusID: String, state: StatusState) {
|
||||
guard var status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||
guard var status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||
|
||||
let realStatusID: String
|
||||
if let rebloggedStatusID = status.reblog?.id,
|
||||
let rebloggedStatus = MastodonCache.status(for: rebloggedStatusID) {
|
||||
let rebloggedStatus = mastodonController.cache.status(for: rebloggedStatusID) {
|
||||
reblogStatusID = statusID
|
||||
rebloggerID = status.account.id
|
||||
status = rebloggedStatus
|
||||
|
@ -78,7 +85,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
@objc override func updateUIForPreferences() {
|
||||
super.updateUIForPreferences()
|
||||
if let rebloggerID = rebloggerID,
|
||||
let reblogger = MastodonCache.account(for: rebloggerID) {
|
||||
let reblogger = mastodonController.cache.account(for: rebloggerID) {
|
||||
updateRebloggerLabel(reblogger: reblogger)
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +95,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
guard let status = MastodonCache.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.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())
|
||||
|
@ -103,7 +110,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
delay = nil
|
||||
}
|
||||
if let delay = delay {
|
||||
updateTimestampWorkItem = DispatchWorkItem {
|
||||
updateTimestampWorkItem = DispatchWorkItem { [unowned self] in
|
||||
self.updateTimestamp()
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!)
|
||||
|
@ -115,7 +122,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
func reply() {
|
||||
if Preferences.shared.mentionReblogger,
|
||||
let rebloggerID = rebloggerID,
|
||||
let rebloggerAccount = MastodonCache.account(for: rebloggerID) {
|
||||
let rebloggerAccount = mastodonController.cache.account(for: rebloggerID) {
|
||||
delegate?.reply(to: statusID, mentioningAcct: rebloggerAccount.acct)
|
||||
} else {
|
||||
delegate?.reply(to: statusID)
|
||||
|
@ -139,8 +146,9 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
}
|
||||
|
||||
override func getStatusCellPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> BaseStatusTableViewCell.PreviewProviders? {
|
||||
guard let mastodonController = mastodonController else { return nil }
|
||||
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, sourceView: self) }
|
||||
)
|
||||
}
|
||||
|
@ -156,7 +164,8 @@ extension TimelineStatusTableViewCell: SelectableTableViewCell {
|
|||
extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||
|
||||
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
guard let mastodonController = mastodonController else { return nil }
|
||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
|
||||
let favoriteTitle: String
|
||||
let favoriteRequest: Request<Status>
|
||||
|
@ -172,14 +181,14 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
|||
favoriteColor = UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
|
||||
}
|
||||
let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { (action, view, completion) in
|
||||
MastodonController.client.run(favoriteRequest, completion: { response in
|
||||
mastodonController.run(favoriteRequest, completion: { response in
|
||||
DispatchQueue.main.async {
|
||||
guard case let .success(status, _) = response else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
completion(true)
|
||||
MastodonCache.add(status: status)
|
||||
mastodonController.cache.add(status: status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -199,14 +208,14 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
|||
reblogColor = tintColor
|
||||
}
|
||||
let reblog = UIContextualAction(style: .normal, title: reblogTitle) { (action, view, completion) in
|
||||
MastodonController.client.run(reblogRequest, completion: { response in
|
||||
mastodonController.run(reblogRequest, completion: { response in
|
||||
DispatchQueue.main.async {
|
||||
guard case let .success(status, _) = response else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
completion(true)
|
||||
MastodonCache.add(status: status)
|
||||
mastodonController.cache.add(status: status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ class StatusContentTextView: ContentTextView {
|
|||
var statusID: String? {
|
||||
didSet {
|
||||
guard let statusID = statusID else { return }
|
||||
guard let status = MastodonCache.status(for: statusID) else {
|
||||
guard let mastodonController = mastodonController,
|
||||
let status = mastodonController.cache.status(for: statusID) else {
|
||||
fatalError("Can't set StatusContentTextView text without cached status for \(statusID)")
|
||||
}
|
||||
setTextFromHtml(status.content)
|
||||
|
@ -25,7 +26,8 @@ class StatusContentTextView: ContentTextView {
|
|||
override func getMention(for url: URL, text: String) -> Mention? {
|
||||
let mention: Mention?
|
||||
if let statusID = statusID,
|
||||
let status = MastodonCache.status(for: statusID) {
|
||||
let mastodonController = mastodonController,
|
||||
let status = mastodonController.cache.status(for: statusID) {
|
||||
mention = status.mentions.first { (mention) in
|
||||
// Mastodon and Pleroma include the @ in the <a> text, GNU Social does not
|
||||
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host
|
||||
|
@ -39,7 +41,8 @@ class StatusContentTextView: ContentTextView {
|
|||
override func getHashtag(for url: URL, text: String) -> Hashtag? {
|
||||
let hashtag: Hashtag?
|
||||
if let statusID = statusID,
|
||||
let status = MastodonCache.status(for: statusID) {
|
||||
let mastodonController = mastodonController,
|
||||
let status = mastodonController.cache.status(for: statusID) {
|
||||
hashtag = status.hashtags.first { (hashtag) in
|
||||
hashtag.url == url
|
||||
}
|
||||
|
|
|
@ -13,8 +13,15 @@ import SwiftSoup
|
|||
struct XCBActions {
|
||||
|
||||
// MARK: - Utils
|
||||
private static var mastodonController: MastodonController {
|
||||
let scene = UIApplication.shared.activeOrBackgroundScene!
|
||||
return scene.session.mastodonController!
|
||||
}
|
||||
|
||||
private static func getMainTabBarController() -> MainTabBarViewController {
|
||||
return (UIApplication.shared.delegate as! AppDelegate).window!.rootViewController as! MainTabBarViewController
|
||||
let scene = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first!
|
||||
let window = scene.windows.first { $0.isKeyWindow }!
|
||||
return window.rootViewController as! MainTabBarViewController
|
||||
}
|
||||
|
||||
private static func show(_ vc: UIViewController) {
|
||||
|
@ -31,7 +38,7 @@ struct XCBActions {
|
|||
|
||||
private static func getStatus(from request: XCBRequest, session: XCBSession, completion: @escaping (Status) -> Void) {
|
||||
if let id = request.arguments["statusID"] {
|
||||
MastodonCache.status(for: id) { (status) in
|
||||
mastodonController.cache.status(for: id) { (status) in
|
||||
if let status = status {
|
||||
completion(status)
|
||||
} else {
|
||||
|
@ -41,11 +48,11 @@ struct XCBActions {
|
|||
}
|
||||
}
|
||||
} else if let searchQuery = request.arguments["statusURL"] {
|
||||
let request = MastodonController.client.search(query: searchQuery)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.search(query: searchQuery)
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(results, _) = response,
|
||||
let status = results.statuses.first {
|
||||
MastodonCache.add(status: status)
|
||||
mastodonController.cache.add(status: status)
|
||||
completion(status)
|
||||
} else {
|
||||
session.complete(with: .error, additionalData: [
|
||||
|
@ -62,7 +69,7 @@ struct XCBActions {
|
|||
|
||||
private static func getAccount(from request: XCBRequest, session: XCBSession, completion: @escaping (Account) -> Void) {
|
||||
if let id = request.arguments["accountID"] {
|
||||
MastodonCache.account(for: id) { (account) in
|
||||
mastodonController.cache.account(for: id) { (account) in
|
||||
if let account = account {
|
||||
completion(account)
|
||||
} else {
|
||||
|
@ -72,11 +79,11 @@ struct XCBActions {
|
|||
}
|
||||
}
|
||||
} else if let searchQuery = request.arguments["accountURL"] {
|
||||
let request = MastodonController.client.search(query: searchQuery)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.search(query: searchQuery)
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(results, _) = response {
|
||||
if let account = results.accounts.first {
|
||||
MastodonCache.add(account: account)
|
||||
mastodonController.cache.add(account: account)
|
||||
completion(account)
|
||||
} else {
|
||||
session.complete(with: .error, additionalData: [
|
||||
|
@ -90,11 +97,11 @@ struct XCBActions {
|
|||
}
|
||||
}
|
||||
} else if let acct = request.arguments["acct"] {
|
||||
let request = MastodonController.client.searchForAccount(query: acct)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
let request = Client.searchForAccount(query: acct)
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(accounts, _) = response {
|
||||
if let account = accounts.first {
|
||||
MastodonCache.add(account: account)
|
||||
mastodonController.cache.add(account: account)
|
||||
completion(account)
|
||||
} else {
|
||||
session.complete(with: .error, additionalData: [
|
||||
|
@ -118,7 +125,7 @@ struct XCBActions {
|
|||
static func showStatus(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
||||
getStatus(from: request, session: session) { (status) in
|
||||
DispatchQueue.main.async {
|
||||
let vc = ConversationTableViewController(for: status.id)
|
||||
let vc = ConversationTableViewController(for: status.id, mastodonController: mastodonController)
|
||||
show(vc)
|
||||
}
|
||||
}
|
||||
|
@ -132,14 +139,14 @@ struct XCBActions {
|
|||
var status = ""
|
||||
if let mentioning = mentioning { status += mentioning }
|
||||
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: [
|
||||
"error": "Too many characters. Instance maximum is \(MastodonController.instance.maxStatusCharacters ?? 500)"
|
||||
"error": "Too many characters. Instance maximum is \(mastodonController.instance.maxStatusCharacters ?? 500)"
|
||||
])
|
||||
return
|
||||
}
|
||||
let request = MastodonController.client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility)
|
||||
MastodonController.client.run(request) { response in
|
||||
let request = Client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility)
|
||||
mastodonController.run(request) { response in
|
||||
if case let .success(status, _) = response {
|
||||
session.complete(with: .success, additionalData: [
|
||||
"statusURL": status.url?.absoluteString,
|
||||
|
@ -152,7 +159,7 @@ struct XCBActions {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
let compose = ComposeViewController(mentioningAcct: mentioning, text: text)
|
||||
let compose = ComposeViewController(mentioningAcct: mentioning, text: text, mastodonController: mastodonController)
|
||||
compose.xcbSession = session
|
||||
let vc = UINavigationController(rootViewController: compose)
|
||||
present(vc)
|
||||
|
@ -199,9 +206,9 @@ struct XCBActions {
|
|||
|
||||
static func statusAction(request: @escaping (Status) -> Request<Status>, alertTitle: String, _ url: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
||||
func performAction(status: Status, completion: ((Status) -> Void)?) {
|
||||
MastodonController.client.run(request(status)) { (response) in
|
||||
mastodonController.run(request(status)) { (response) in
|
||||
if case let .success(status, _) = response {
|
||||
MastodonCache.add(status: status)
|
||||
mastodonController.cache.add(status: status)
|
||||
completion?(status)
|
||||
session.complete(with: .success, additionalData: [
|
||||
"statusURL": status.url?.absoluteString,
|
||||
|
@ -219,7 +226,7 @@ struct XCBActions {
|
|||
if silent ?? false {
|
||||
performAction(status: status, completion: nil)
|
||||
} else {
|
||||
let vc = ConversationTableViewController(for: status.id)
|
||||
let vc = ConversationTableViewController(for: status.id, mastodonController: mastodonController)
|
||||
DispatchQueue.main.async {
|
||||
show(vc)
|
||||
}
|
||||
|
@ -247,7 +254,7 @@ struct XCBActions {
|
|||
static func showAccount(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
||||
getAccount(from: request, session: session) { (account) in
|
||||
DispatchQueue.main.async {
|
||||
let vc = ProfileTableViewController(accountID: account.id)
|
||||
let vc = ProfileTableViewController(accountID: account.id, mastodonController: mastodonController)
|
||||
show(vc)
|
||||
}
|
||||
}
|
||||
|
@ -269,7 +276,7 @@ struct XCBActions {
|
|||
}
|
||||
|
||||
static func getCurrentUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
||||
let account = MastodonController.account!
|
||||
let account = mastodonController.account!
|
||||
session.complete(with: .success, additionalData: [
|
||||
"username": account.acct,
|
||||
"displayName": account.displayName,
|
||||
|
@ -285,9 +292,9 @@ struct XCBActions {
|
|||
static func followUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
||||
func performAction(_ account: Account) {
|
||||
let request = Account.follow(account.id)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
mastodonController.run(request) { (response) in
|
||||
if case let .success(relationship, _) = response {
|
||||
MastodonCache.add(relationship: relationship)
|
||||
mastodonController.cache.add(relationship: relationship)
|
||||
session.complete(with: .success, additionalData: [
|
||||
"url": account.url.absoluteString
|
||||
])
|
||||
|
@ -303,7 +310,7 @@ struct XCBActions {
|
|||
if silent ?? false {
|
||||
performAction(account)
|
||||
} else {
|
||||
let vc = ProfileTableViewController(accountID: account.id)
|
||||
let vc = ProfileTableViewController(accountID: account.id, mastodonController: mastodonController)
|
||||
DispatchQueue.main.async {
|
||||
show(vc)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue