Compare commits

...

6 Commits

Author SHA1 Message Date
Shadowfacts 12b6623113
Merge branch 'master' into multiple-accounts 2020-01-20 12:16:11 -05:00
Shadowfacts cece8825ad
Fix decoding Account.moved on Mastodon 2020-01-20 12:10:10 -05:00
Shadowfacts f9ffb240ef
Fix decoding Hashtag.History on Mastodon 2020-01-20 12:07:30 -05:00
Shadowfacts 79f44c9b58
Change recommended instance selector to store categories as strings
instead of enum

Additional categories can be added, which would cause a crash when
decoding.
As the category isn't used for anything, storing it as an enum value is
not necessary.
2020-01-20 11:56:43 -05:00
Shadowfacts f7421d83ef
Add preference to mention reblogger when replying to a reblogged status 2020-01-19 23:48:36 -05:00
Shadowfacts 7934bc15ac
Split Composing prefs into Composing and Replying 2020-01-19 23:23:31 -05:00
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