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