Merge branch 'master' into multiple-accounts

This commit is contained in:
Shadowfacts 2020-01-20 12:16:03 -05:00
commit 12b6623113
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
7 changed files with 123 additions and 21 deletions

View File

@ -26,9 +26,44 @@ public class Account: Decodable {
public let headerStatic: URL public let headerStatic: URL
public private(set) var emojis: [Emoji] public private(set) var emojis: [Emoji]
public let moved: Bool? public let moved: Bool?
public let movedTo: Account?
public let fields: [Field]? public let fields: [Field]?
public let bot: Bool? public let bot: Bool?
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.username = try container.decode(String.self, forKey: .username)
self.acct = try container.decode(String.self, forKey: .acct)
self.displayName = try container.decode(String.self, forKey: .displayName)
self.locked = try container.decode(Bool.self, forKey: .locked)
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
self.followersCount = try container.decode(Int.self, forKey: .followersCount)
self.followingCount = try container.decode(Int.self, forKey: .followingCount)
self.statusesCount = try container.decode(Int.self, forKey: .statusesCount)
self.note = try container.decode(String.self, forKey: .note)
self.url = try container.decode(URL.self, forKey: .url)
self.avatar = try container.decode(URL.self, forKey: .avatar)
self.avatarStatic = try container.decode(URL.self, forKey: .avatarStatic)
self.header = try container.decode(URL.self, forKey: .header)
self.headerStatic = try container.decode(URL.self, forKey: .url)
self.emojis = try container.decode([Emoji].self, forKey: .emojis)
self.fields = try? container.decode([Field].self, forKey: .fields)
self.bot = try? container.decode(Bool.self, forKey: .bot)
if let moved = try? container.decode(Bool.self, forKey: .moved) {
self.moved = moved
self.movedTo = nil
} else if let account = try? container.decode(Account.self, forKey: .moved) {
self.moved = true
self.movedTo = account
} else {
self.moved = false
self.movedTo = nil
}
}
public static func authorizeFollowRequest(_ account: Account) -> Request<Relationship> { public static func authorizeFollowRequest(_ account: Account) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/follow_requests/\(account.id)/authorize") return Request<Relationship>(method: .post, path: "/api/v1/follow_requests/\(account.id)/authorize")
} }

View File

@ -32,6 +32,39 @@ extension Hashtag {
public let uses: Int public let uses: Int
public let accounts: Int public let accounts: Int
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let day = try? container.decode(Date.self, forKey: .day) {
self.day = day
} else if let unixTimestamp = try? container.decode(Double.self, forKey: .day) {
self.day = Date(timeIntervalSince1970: unixTimestamp)
} else if let str = try? container.decode(String.self, forKey: .day),
let unixTimestamp = Double(str) {
self.day = Date(timeIntervalSince1970: unixTimestamp)
} else {
throw DecodingError.dataCorruptedError(forKey: .day, in: container, debugDescription: "day must be either date or UNIX timestamp")
}
if let uses = try? container.decode(Int.self, forKey: .uses) {
self.uses = uses
} else if let str = try? container.decode(String.self, forKey: .uses),
let uses = Int(str) {
self.uses = uses
} else {
throw DecodingError.dataCorruptedError(forKey: .uses, in: container, debugDescription: "uses must either be int or string containing int")
}
if let accounts = try? container.decode(Int.self, forKey: .accounts) {
self.accounts = accounts
} else if let str = try? container.decode(String.self, forKey: .accounts),
let accounts = Int(str) {
self.accounts = accounts
} else {
throw DecodingError.dataCorruptedError(forKey: .accounts, in: container, debugDescription: "accounts must either be int or string containing int")
}
}
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case day case day
case uses case uses

View File

@ -44,8 +44,9 @@ class Preferences: Codable, ObservableObject {
self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility) self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility)
self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts) self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts)
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions) self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia) self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia)
self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps) self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps)
self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari) self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
@ -68,8 +69,9 @@ class Preferences: Codable, ObservableObject {
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility) try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts) try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts)
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions) try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
try container.encode(mentionReblogger, forKey: .mentionReblogger)
try container.encode(blurAllMedia, forKey: .blurAllMedia) try container.encode(blurAllMedia, forKey: .blurAllMedia)
try container.encode(openLinksInApps, forKey: .openLinksInApps) try container.encode(openLinksInApps, forKey: .openLinksInApps)
try container.encode(useInAppSafari, forKey: .useInAppSafari) try container.encode(useInAppSafari, forKey: .useInAppSafari)
@ -91,8 +93,9 @@ class Preferences: Codable, ObservableObject {
// MARK: - Behavior // MARK: - Behavior
@Published var defaultPostVisibility = Status.Visibility.public @Published var defaultPostVisibility = Status.Visibility.public
@Published var automaticallySaveDrafts = true @Published var automaticallySaveDrafts = true
@Published var contentWarningCopyMode = ContentWarningCopyMode.asIs
@Published var requireAttachmentDescriptions = false @Published var requireAttachmentDescriptions = false
@Published var contentWarningCopyMode = ContentWarningCopyMode.asIs
@Published var mentionReblogger = false
@Published var blurAllMedia = false @Published var blurAllMedia = false
@Published var openLinksInApps = true @Published var openLinksInApps = true
@Published var useInAppSafari = true @Published var useInAppSafari = true
@ -114,8 +117,9 @@ class Preferences: Codable, ObservableObject {
case defaultPostVisibility case defaultPostVisibility
case automaticallySaveDrafts case automaticallySaveDrafts
case contentWarningCopyMode
case requireAttachmentDescriptions case requireAttachmentDescriptions
case contentWarningCopyMode
case mentionReblogger
case blurAllMedia case blurAllMedia
case openLinksInApps case openLinksInApps
case useInAppSafari case useInAppSafari

View File

@ -15,7 +15,7 @@ class ComposeViewController: UIViewController {
let mastodonController: MastodonController let mastodonController: MastodonController
var inReplyToID: String? var inReplyToID: String?
var accountsToMention: [String] var accountsToMention = [String]()
var initialText: String? var initialText: String?
var contentWarningEnabled = false { var contentWarningEnabled = false {
didSet { didSet {
@ -78,11 +78,12 @@ class ComposeViewController: UIViewController {
self.inReplyToID = inReplyToID self.inReplyToID = inReplyToID
if let inReplyToID = inReplyToID, let inReplyTo = mastodonController.cache.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 if let mentioningAcct = mentioningAcct {
accountsToMention = [mentioningAcct]
} else { } else {
accountsToMention = [] accountsToMention = []
} }
if let mentioningAcct = mentioningAcct {
accountsToMention.append(mentioningAcct)
}
if let ownAccount = mastodonController.account { if let ownAccount = mastodonController.account {
accountsToMention.removeAll(where: { acct in ownAccount.acct == acct }) accountsToMention.removeAll(where: { acct in ownAccount.acct == acct })
} }

View File

@ -13,14 +13,15 @@ struct BehaviorPrefsView: View {
var body: some View { var body: some View {
List { List {
section1 composingSection
section2 replyingSection
section3 readingSection
linksSection
}.listStyle(GroupedListStyle()) }.listStyle(GroupedListStyle())
.navigationBarTitle(Text("Behavior")) .navigationBarTitle(Text("Behavior"))
} }
var section1: some View { var composingSection: some View {
Section(header: Text("COMPOSING")) { Section(header: Text("COMPOSING")) {
Picker(selection: $preferences.defaultPostVisibility, label: Text("Default Post Visibility")) { Picker(selection: $preferences.defaultPostVisibility, label: Text("Default Post Visibility")) {
ForEach(Status.Visibility.allCases, id: \.self) { visibility in ForEach(Status.Visibility.allCases, id: \.self) { visibility in
@ -35,18 +36,26 @@ struct BehaviorPrefsView: View {
Toggle(isOn: $preferences.automaticallySaveDrafts) { Toggle(isOn: $preferences.automaticallySaveDrafts) {
Text("Automatically Save Drafts") Text("Automatically Save Drafts")
} }
Picker(selection: $preferences.contentWarningCopyMode, label: Text("Content Warning Copy Style")) {
Text("As-is").tag(ContentWarningCopyMode.asIs)
Text("Prepend 're: '").tag(ContentWarningCopyMode.prependRe)
Text("Don't copy").tag(ContentWarningCopyMode.doNotCopy)
}
Toggle(isOn: $preferences.requireAttachmentDescriptions) { Toggle(isOn: $preferences.requireAttachmentDescriptions) {
Text("Require Attachment Descriptions") Text("Require Attachment Descriptions")
} }
} }
} }
var section2: some View { var replyingSection: some View {
Section(header: Text("REPLYING")) {
Picker(selection: $preferences.contentWarningCopyMode, label: Text("Content Warning Copy Style")) {
Text("As-is").tag(ContentWarningCopyMode.asIs)
Text("Prepend 're: '").tag(ContentWarningCopyMode.prependRe)
Text("Don't copy").tag(ContentWarningCopyMode.doNotCopy)
}
Toggle(isOn: $preferences.mentionReblogger) {
Text("Mention Reblogger")
}
}
}
var readingSection: some View {
Section(header: Text("READING")) { Section(header: Text("READING")) {
Toggle(isOn: $preferences.blurAllMedia) { Toggle(isOn: $preferences.blurAllMedia) {
Text("Blur All Media") Text("Blur All Media")
@ -54,7 +63,7 @@ struct BehaviorPrefsView: View {
} }
} }
var section3: some View { var linksSection: some View {
Section(header: Text("LINKS")) { Section(header: Text("LINKS")) {
Toggle(isOn: $preferences.openLinksInApps) { Toggle(isOn: $preferences.openLinksInApps) {
Text("Open Links in Apps") Text("Open Links in Apps")

View File

@ -34,6 +34,8 @@ protocol TuskerNavigationDelegate: class {
func reply(to statusID: String) func reply(to statusID: String)
func reply(to statusID: String, mentioningAcct: String?)
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController
func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController
@ -127,7 +129,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
compose(mentioning: nil) compose(mentioning: nil)
} }
func compose(mentioning: String? = nil) { func compose(mentioning: String?) {
let compose = ComposeViewController(mentioningAcct: mentioning, mastodonController: apiController) 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
@ -135,7 +137,11 @@ extension TuskerNavigationDelegate where Self: UIViewController {
} }
func reply(to statusID: String) { func reply(to statusID: String) {
let compose = ComposeViewController(inReplyTo: statusID, mastodonController: apiController) reply(to: statusID, mentioningAcct: nil)
}
func reply(to statusID: String, mentioningAcct: String?) {
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)

View File

@ -120,6 +120,16 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
} }
} }
func reply() {
if Preferences.shared.mentionReblogger,
let rebloggerID = rebloggerID,
let rebloggerAccount = mastodonController.cache.account(for: rebloggerID) {
delegate?.reply(to: statusID, mentioningAcct: rebloggerAccount.acct)
} else {
delegate?.reply(to: statusID)
}
}
override func prepareForReuse() { override func prepareForReuse() {
super.prepareForReuse() super.prepareForReuse()
updateTimestampWorkItem?.cancel() updateTimestampWorkItem?.cancel()
@ -132,6 +142,10 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
delegate?.selected(account: rebloggerID) delegate?.selected(account: rebloggerID)
} }
override func replyPressed() {
reply()
}
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 } guard let mastodonController = mastodonController else { return nil }
return ( return (
@ -215,7 +229,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
func trailingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? { func trailingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
let reply = UIContextualAction(style: .normal, title: "Reply") { (action, view, completion) in let reply = UIContextualAction(style: .normal, title: "Reply") { (action, view, completion) in
completion(true) completion(true)
self.delegate?.reply(to: self.statusID) self.reply()
} }
reply.image = UIImage(systemName: "arrowshape.turn.up.left.fill") reply.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
reply.backgroundColor = tintColor reply.backgroundColor = tintColor