Compare commits
2 Commits
90f17693f1
...
e18a09f4ac
Author | SHA1 | Date |
---|---|---|
Shadowfacts | e18a09f4ac | |
Shadowfacts | 005001b081 |
|
@ -298,7 +298,10 @@ public class Client {
|
||||||
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,
|
||||||
|
pollOptions: [String]? = nil,
|
||||||
|
pollExpiresIn: Int? = nil,
|
||||||
|
pollMultiple: Bool? = nil) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses", body: ParametersBody([
|
return Request<Status>(method: .post, path: "/api/v1/statuses", body: ParametersBody([
|
||||||
"status" => text,
|
"status" => text,
|
||||||
"content_type" => contentType.mimeType,
|
"content_type" => contentType.mimeType,
|
||||||
|
@ -306,8 +309,10 @@ public class Client {
|
||||||
"sensitive" => sensitive,
|
"sensitive" => sensitive,
|
||||||
"spoiler_text" => spoilerText,
|
"spoiler_text" => spoilerText,
|
||||||
"visibility" => visibility?.rawValue,
|
"visibility" => visibility?.rawValue,
|
||||||
"language" => language
|
"language" => language,
|
||||||
] + "media_ids" => media?.map { $0.id }))
|
"poll[expires_in]" => pollExpiresIn,
|
||||||
|
"poll[multiple]" => pollMultiple,
|
||||||
|
] + "media_ids" => media?.map { $0.id } + "poll[options]" => pollOptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Timelines
|
// MARK: - Timelines
|
||||||
|
|
|
@ -160,6 +160,7 @@
|
||||||
D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */; };
|
D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */; };
|
||||||
D662AEEF263A3B880082A153 /* PollFinishedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D662AEED263A3B880082A153 /* PollFinishedTableViewCell.swift */; };
|
D662AEEF263A3B880082A153 /* PollFinishedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D662AEED263A3B880082A153 /* PollFinishedTableViewCell.swift */; };
|
||||||
D662AEF0263A3B880082A153 /* PollFinishedTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D662AEEE263A3B880082A153 /* PollFinishedTableViewCell.xib */; };
|
D662AEF0263A3B880082A153 /* PollFinishedTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D662AEEE263A3B880082A153 /* PollFinishedTableViewCell.xib */; };
|
||||||
|
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D662AEF1263A4BE10082A153 /* ComposePollView.swift */; };
|
||||||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */; };
|
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */; };
|
||||||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */; };
|
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */; };
|
||||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626121360B1900C9CBA2 /* Preferences.swift */; };
|
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626121360B1900C9CBA2 /* Preferences.swift */; };
|
||||||
|
@ -275,7 +276,7 @@
|
||||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; };
|
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; };
|
||||||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
||||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */; };
|
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */; };
|
||||||
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */; };
|
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143D9253510F4007DC240 /* ComposeEmojiTextField.swift */; };
|
||||||
D6C143E025354E34007DC240 /* EmojiPickerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */; };
|
D6C143E025354E34007DC240 /* EmojiPickerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */; };
|
||||||
D6C143FD25354FD0007DC240 /* EmojiCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */; };
|
D6C143FD25354FD0007DC240 /* EmojiCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */; };
|
||||||
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
||||||
|
@ -538,6 +539,7 @@
|
||||||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusStateResolver.swift; sourceTree = "<group>"; };
|
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusStateResolver.swift; sourceTree = "<group>"; };
|
||||||
D662AEED263A3B880082A153 /* PollFinishedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFinishedTableViewCell.swift; sourceTree = "<group>"; };
|
D662AEED263A3B880082A153 /* PollFinishedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFinishedTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D662AEEE263A3B880082A153 /* PollFinishedTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PollFinishedTableViewCell.xib; sourceTree = "<group>"; };
|
D662AEEE263A3B880082A153 /* PollFinishedTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PollFinishedTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
D662AEF1263A4BE10082A153 /* ComposePollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposePollView.swift; sourceTree = "<group>"; };
|
||||||
D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConversationMainStatusTableViewCell.xib; sourceTree = "<group>"; };
|
D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConversationMainStatusTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMainStatusTableViewCell.swift; sourceTree = "<group>"; };
|
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMainStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D663626121360B1900C9CBA2 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
D663626121360B1900C9CBA2 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||||
|
@ -648,7 +650,7 @@
|
||||||
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = "<group>"; };
|
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = "<group>"; };
|
D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = "<group>"; };
|
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeContentWarningTextField.swift; sourceTree = "<group>"; };
|
D6C143D9253510F4007DC240 /* ComposeEmojiTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeEmojiTextField.swift; sourceTree = "<group>"; };
|
||||||
D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCollectionViewController.swift; sourceTree = "<group>"; };
|
D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCardView.swift; sourceTree = "<group>"; };
|
D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCardView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1111,13 +1113,14 @@
|
||||||
D6A57407255C53EC00674551 /* ComposeTextViewCaretScrolling.swift */,
|
D6A57407255C53EC00674551 /* ComposeTextViewCaretScrolling.swift */,
|
||||||
D62275A924F1E01C00B82A16 /* ComposeTextView.swift */,
|
D62275A924F1E01C00B82A16 /* ComposeTextView.swift */,
|
||||||
D6C99FCA24FADC91005C74D3 /* MainComposeTextView.swift */,
|
D6C99FCA24FADC91005C74D3 /* MainComposeTextView.swift */,
|
||||||
|
D662AEF1263A4BE10082A153 /* ComposePollView.swift */,
|
||||||
D622757324EDF1CD00B82A16 /* ComposeAttachmentsList.swift */,
|
D622757324EDF1CD00B82A16 /* ComposeAttachmentsList.swift */,
|
||||||
D622757924EE21D900B82A16 /* ComposeAttachmentRow.swift */,
|
D622757924EE21D900B82A16 /* ComposeAttachmentRow.swift */,
|
||||||
D622757724EE133700B82A16 /* ComposeAssetPicker.swift */,
|
D622757724EE133700B82A16 /* ComposeAssetPicker.swift */,
|
||||||
D62275A524F1C81800B82A16 /* ComposeReplyView.swift */,
|
D62275A524F1C81800B82A16 /* ComposeReplyView.swift */,
|
||||||
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */,
|
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */,
|
||||||
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */,
|
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */,
|
||||||
D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */,
|
D6C143D9253510F4007DC240 /* ComposeEmojiTextField.swift */,
|
||||||
D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */,
|
D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */,
|
||||||
D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */,
|
D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */,
|
||||||
D670F8B52537DC890046588A /* EmojiPickerWrapper.swift */,
|
D670F8B52537DC890046588A /* EmojiPickerWrapper.swift */,
|
||||||
|
@ -1980,6 +1983,7 @@
|
||||||
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */,
|
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */,
|
||||||
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */,
|
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */,
|
||||||
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */,
|
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */,
|
||||||
|
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */,
|
||||||
D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */,
|
D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */,
|
||||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
||||||
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
|
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
|
||||||
|
@ -2041,7 +2045,7 @@
|
||||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
||||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
||||||
D69693F42585941A00F4E116 /* UIWindowSceneDelegate+Close.swift in Sources */,
|
D69693F42585941A00F4E116 /* UIWindowSceneDelegate+Close.swift in Sources */,
|
||||||
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */,
|
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.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 */,
|
||||||
|
|
|
@ -20,13 +20,15 @@ class Draft: Codable, ObservableObject {
|
||||||
@Published var attachments: [CompositionAttachment]
|
@Published var attachments: [CompositionAttachment]
|
||||||
@Published var inReplyToID: String?
|
@Published var inReplyToID: String?
|
||||||
@Published var visibility: Status.Visibility
|
@Published var visibility: Status.Visibility
|
||||||
|
@Published var poll: Poll?
|
||||||
|
|
||||||
var initialText: String
|
var initialText: String
|
||||||
|
|
||||||
var hasContent: Bool {
|
var hasContent: Bool {
|
||||||
(!text.isEmpty && text != initialText) ||
|
(!text.isEmpty && text != initialText) ||
|
||||||
(contentWarningEnabled && !contentWarning.isEmpty) ||
|
(contentWarningEnabled && !contentWarning.isEmpty) ||
|
||||||
attachments.count > 0
|
attachments.count > 0 ||
|
||||||
|
poll?.hasContent == true
|
||||||
}
|
}
|
||||||
|
|
||||||
var textForPosting: String {
|
var textForPosting: String {
|
||||||
|
@ -46,6 +48,7 @@ class Draft: Codable, ObservableObject {
|
||||||
self.attachments = []
|
self.attachments = []
|
||||||
self.inReplyToID = nil
|
self.inReplyToID = nil
|
||||||
self.visibility = Preferences.shared.defaultPostVisibility
|
self.visibility = Preferences.shared.defaultPostVisibility
|
||||||
|
self.poll = nil
|
||||||
|
|
||||||
self.initialText = ""
|
self.initialText = ""
|
||||||
}
|
}
|
||||||
|
@ -75,6 +78,7 @@ class Draft: Codable, ObservableObject {
|
||||||
self.attachments = try container.decode([CompositionAttachment].self, forKey: .attachments)
|
self.attachments = try container.decode([CompositionAttachment].self, forKey: .attachments)
|
||||||
self.inReplyToID = try container.decode(String?.self, forKey: .inReplyToID)
|
self.inReplyToID = try container.decode(String?.self, forKey: .inReplyToID)
|
||||||
self.visibility = try container.decode(Status.Visibility.self, forKey: .visibility)
|
self.visibility = try container.decode(Status.Visibility.self, forKey: .visibility)
|
||||||
|
self.poll = try container.decode(Poll.self, forKey: .poll)
|
||||||
|
|
||||||
self.initialText = try container.decode(String.self, forKey: .initialText)
|
self.initialText = try container.decode(String.self, forKey: .initialText)
|
||||||
}
|
}
|
||||||
|
@ -92,6 +96,7 @@ class Draft: Codable, ObservableObject {
|
||||||
try container.encode(attachments, forKey: .attachments)
|
try container.encode(attachments, forKey: .attachments)
|
||||||
try container.encode(inReplyToID, forKey: .inReplyToID)
|
try container.encode(inReplyToID, forKey: .inReplyToID)
|
||||||
try container.encode(visibility, forKey: .visibility)
|
try container.encode(visibility, forKey: .visibility)
|
||||||
|
try container.encode(poll, forKey: .poll)
|
||||||
|
|
||||||
try container.encode(initialText, forKey: .initialText)
|
try container.encode(initialText, forKey: .initialText)
|
||||||
}
|
}
|
||||||
|
@ -115,11 +120,68 @@ extension Draft {
|
||||||
case attachments
|
case attachments
|
||||||
case inReplyToID
|
case inReplyToID
|
||||||
case visibility
|
case visibility
|
||||||
|
case poll
|
||||||
|
|
||||||
case initialText
|
case initialText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Draft {
|
||||||
|
class Poll: Codable, ObservableObject {
|
||||||
|
@Published var options: [Option]
|
||||||
|
@Published var multiple: Bool
|
||||||
|
@Published var duration: TimeInterval
|
||||||
|
|
||||||
|
var hasContent: Bool {
|
||||||
|
options.contains { !$0.text.isEmpty }
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.options = [Option(""), Option("")]
|
||||||
|
self.multiple = false
|
||||||
|
self.duration = 24 * 60 * 60 // 1 day
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
self.options = try container.decode([Option].self, forKey: .options)
|
||||||
|
self.multiple = try container.decode(Bool.self, forKey: .multiple)
|
||||||
|
self.duration = try container.decode(TimeInterval.self, forKey: .duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(options, forKey: .options)
|
||||||
|
try container.encode(multiple, forKey: .multiple)
|
||||||
|
try container.encode(duration, forKey: .duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case options
|
||||||
|
case multiple
|
||||||
|
case duration
|
||||||
|
}
|
||||||
|
|
||||||
|
class Option: Identifiable, Codable, ObservableObject {
|
||||||
|
let id = UUID()
|
||||||
|
@Published var text: String
|
||||||
|
|
||||||
|
init(_ text: String) {
|
||||||
|
self.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(from decoder: Decoder) throws {
|
||||||
|
self.text = try decoder.singleValueContainer().decode(String.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
try container.encode(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension MastodonController {
|
extension MastodonController {
|
||||||
|
|
||||||
func createDraft(inReplyToID: String? = nil, mentioningAcct: String? = nil) -> Draft {
|
func createDraft(inReplyToID: String? = nil, mentioningAcct: String? = nil) -> Draft {
|
||||||
|
|
|
@ -60,6 +60,14 @@ struct ComposeAttachmentsList: View {
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
.frame(height: cellHeight / 2)
|
.frame(height: cellHeight / 2)
|
||||||
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
||||||
|
|
||||||
|
Button(action: self.togglePoll) {
|
||||||
|
Label(draft.poll == nil ? "Add a poll" : "Remove poll", systemImage: "chart.bar.doc.horizontal")
|
||||||
|
}
|
||||||
|
.disabled(!canAddPoll)
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.frame(height: cellHeight / 2)
|
||||||
|
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
||||||
}
|
}
|
||||||
.frame(height: totalListHeight)
|
.frame(height: totalListHeight)
|
||||||
.onAppear(perform: self.didAppear)
|
.onAppear(perform: self.didAppear)
|
||||||
|
@ -84,14 +92,25 @@ struct ComposeAttachmentsList: View {
|
||||||
case .pleroma:
|
case .pleroma:
|
||||||
return true
|
return true
|
||||||
case .mastodon:
|
case .mastodon:
|
||||||
return draft.attachments.count < 4 && draft.attachments.allSatisfy { $0.data.type == .image }
|
return draft.attachments.count < 4 && draft.attachments.allSatisfy { $0.data.type == .image } && draft.poll == nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var canAddPoll: Bool {
|
||||||
|
switch mastodonController.instance?.instanceType {
|
||||||
|
case nil:
|
||||||
|
return false
|
||||||
|
case .pleroma:
|
||||||
|
return true
|
||||||
|
case .mastodon:
|
||||||
|
return draft.attachments.isEmpty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var totalListHeight: CGFloat {
|
private var totalListHeight: CGFloat {
|
||||||
let totalRowHeights = rowHeights.values.reduce(0, +)
|
let totalRowHeights = rowHeights.values.reduce(0, +)
|
||||||
let totalPadding = CGFloat(draft.attachments.count) * cellPadding
|
let totalPadding = CGFloat(draft.attachments.count) * cellPadding
|
||||||
let addButtonHeight = cellHeight + cellPadding * 2
|
let addButtonHeight = 3 * (cellHeight / 2 + cellPadding)
|
||||||
return totalRowHeights + totalPadding + addButtonHeight
|
return totalRowHeights + totalPadding + addButtonHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +174,14 @@ struct ComposeAttachmentsList: View {
|
||||||
uiState.composeDrawingMode = .createNew
|
uiState.composeDrawingMode = .createNew
|
||||||
uiState.delegate?.presentComposeDrawing()
|
uiState.delegate?.presentComposeDrawing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func togglePoll() {
|
||||||
|
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||||
|
|
||||||
|
withAnimation {
|
||||||
|
draft.poll = draft.poll == nil ? Draft.Poll() : nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//struct ComposeAttachmentsList_Previews: PreviewProvider {
|
//struct ComposeAttachmentsList_Previews: PreviewProvider {
|
||||||
|
|
|
@ -8,22 +8,50 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ComposeContentWarningTextField: UIViewRepresentable {
|
struct ComposeEmojiTextField: UIViewRepresentable {
|
||||||
typealias UIViewType = UITextField
|
typealias UIViewType = UITextField
|
||||||
|
|
||||||
@Binding var text: String
|
|
||||||
|
|
||||||
@EnvironmentObject private var uiState: ComposeUIState
|
@EnvironmentObject private var uiState: ComposeUIState
|
||||||
|
|
||||||
|
@Binding private var text: String
|
||||||
|
private let placeholder: String
|
||||||
|
private var didChange: ((String) -> Void)?
|
||||||
|
private var didEndEditing: (() -> Void)?
|
||||||
|
private var backgroundColor: UIColor? = nil
|
||||||
|
|
||||||
|
init(text: Binding<String>, placeholder: String) {
|
||||||
|
self._text = text
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.didChange = nil
|
||||||
|
self.didEndEditing = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func didChange(_ didChange: @escaping (String) -> Void) -> Self {
|
||||||
|
self.didChange = didChange
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func didEndEditing(_ didEndEditing: @escaping () -> Void) -> Self {
|
||||||
|
self.didEndEditing = didEndEditing
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func backgroundColor(_ color: UIColor) -> Self {
|
||||||
|
self.backgroundColor = color
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UITextField {
|
func makeUIView(context: Context) -> UITextField {
|
||||||
let view = UITextField()
|
let view = UITextField()
|
||||||
|
|
||||||
view.placeholder = "Write your warning here"
|
view.placeholder = placeholder
|
||||||
view.borderStyle = .roundedRect
|
view.borderStyle = .roundedRect
|
||||||
|
|
||||||
view.delegate = context.coordinator
|
view.delegate = context.coordinator
|
||||||
view.addTarget(context.coordinator, action: #selector(Coordinator.didChange(_:)), for: .editingChanged)
|
view.addTarget(context.coordinator, action: #selector(Coordinator.didChange(_:)), for: .editingChanged)
|
||||||
|
|
||||||
|
view.backgroundColor = backgroundColor
|
||||||
|
|
||||||
context.coordinator.textField = view
|
context.coordinator.textField = view
|
||||||
context.coordinator.uiState = uiState
|
context.coordinator.uiState = uiState
|
||||||
context.coordinator.text = $text
|
context.coordinator.text = $text
|
||||||
|
@ -37,6 +65,8 @@ struct ComposeContentWarningTextField: UIViewRepresentable {
|
||||||
} else {
|
} else {
|
||||||
uiView.text = text
|
uiView.text = text
|
||||||
}
|
}
|
||||||
|
context.coordinator.didChange = didChange
|
||||||
|
context.coordinator.didEndEditing = didEndEditing
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
func makeCoordinator() -> Coordinator {
|
||||||
|
@ -47,11 +77,14 @@ struct ComposeContentWarningTextField: UIViewRepresentable {
|
||||||
weak var textField: UITextField?
|
weak var textField: UITextField?
|
||||||
var text: Binding<String>!
|
var text: Binding<String>!
|
||||||
var uiState: ComposeUIState!
|
var uiState: ComposeUIState!
|
||||||
|
var didChange: ((String) -> Void)?
|
||||||
|
var didEndEditing: (() -> Void)?
|
||||||
|
|
||||||
var skipSettingTextOnNextUpdate = false
|
var skipSettingTextOnNextUpdate = false
|
||||||
|
|
||||||
@objc func didChange(_ textField: UITextField) {
|
@objc func didChange(_ textField: UITextField) {
|
||||||
text.wrappedValue = textField.text ?? ""
|
text.wrappedValue = textField.text ?? ""
|
||||||
|
didChange?(text.wrappedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
|
@ -61,6 +94,7 @@ struct ComposeContentWarningTextField: UIViewRepresentable {
|
||||||
|
|
||||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
updateAutocompleteState(textField: textField)
|
updateAutocompleteState(textField: textField)
|
||||||
|
didEndEditing?()
|
||||||
}
|
}
|
||||||
|
|
||||||
func textFieldDidChangeSelection(_ textField: UITextField) {
|
func textFieldDidChangeSelection(_ textField: UITextField) {
|
|
@ -0,0 +1,209 @@
|
||||||
|
//
|
||||||
|
// ComposePollView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 4/28/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ComposePollView: View {
|
||||||
|
private static let formatter: DateComponentsFormatter = {
|
||||||
|
let f = DateComponentsFormatter()
|
||||||
|
f.maximumUnitCount = 1
|
||||||
|
f.unitsStyle = .full
|
||||||
|
f.allowedUnits = [.weekOfMonth, .day, .hour, .minute]
|
||||||
|
return f
|
||||||
|
}()
|
||||||
|
|
||||||
|
@ObservedObject var draft: Draft
|
||||||
|
@ObservedObject var poll: Draft.Poll
|
||||||
|
|
||||||
|
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||||
|
|
||||||
|
@State private var duration: Duration {
|
||||||
|
didSet {
|
||||||
|
poll.duration = duration.timeInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(draft: Draft, poll: Draft.Poll) {
|
||||||
|
self.draft = draft
|
||||||
|
self.poll = poll
|
||||||
|
|
||||||
|
self._duration = State(initialValue: .fromTimeInterval(poll.duration) ?? .oneDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Text("Poll")
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: self.removePoll) {
|
||||||
|
Image(systemName: "xmark")
|
||||||
|
.imageScale(.small)
|
||||||
|
.padding(4)
|
||||||
|
}
|
||||||
|
.accentColor(buttonForegroundColor)
|
||||||
|
.background(Circle().foregroundColor(buttonBackgroundColor))
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEach(Array(poll.options.enumerated()), id: \.element.id) { (e) in
|
||||||
|
ComposePollOption(poll: poll, option: e.element, optionIndex: e.offset)
|
||||||
|
}
|
||||||
|
.transition(.slide)
|
||||||
|
|
||||||
|
Button(action: self.addOption) {
|
||||||
|
Label("Add Option", systemImage: "plus")
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
// use .animation(nil) on the binding and .frame(maxWidth: .infinity) on labels so frame doesn't have a size change animation when the text changes
|
||||||
|
Picker(selection: $poll.multiple.animation(nil), label: Text(poll.multiple ? "Allow multiple choices" : "Single choice").frame(maxWidth: .infinity)) {
|
||||||
|
Text("Allow multiple choices").tag(true)
|
||||||
|
Text("Single choice").tag(false)
|
||||||
|
}
|
||||||
|
.pickerStyle(MenuPickerStyle())
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
Picker(selection: $duration.animation(nil), label: Text(verbatim: ComposePollView.formatter.string(from: duration.timeInterval)!).frame(maxWidth: .infinity)) {
|
||||||
|
ForEach(Duration.allCases, id: \.self) { (duration) in
|
||||||
|
Text(ComposePollView.formatter.string(from: duration.timeInterval)!).tag(duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(MenuPickerStyle())
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(8)
|
||||||
|
.background(
|
||||||
|
backgroundColor
|
||||||
|
.cornerRadius(10)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var backgroundColor: Color {
|
||||||
|
// in light mode, .secondarySystemBackground has a blue-ish hue, which we don't want
|
||||||
|
colorScheme == .dark ? Color(UIColor.secondarySystemBackground) : Color(white: 0.95)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var buttonBackgroundColor: Color {
|
||||||
|
Color(white: colorScheme == .dark ? 0.1 : 0.8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var buttonForegroundColor: Color {
|
||||||
|
Color(UIColor.label)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removePoll() {
|
||||||
|
withAnimation {
|
||||||
|
self.draft.poll = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addOption() {
|
||||||
|
withAnimation(.easeInOut(duration: 0.25)) {
|
||||||
|
poll.options.append(Draft.Poll.Option(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ComposePollView {
|
||||||
|
enum Duration: Hashable, Equatable, CaseIterable {
|
||||||
|
case fiveMinutes, thirtyMinutes, oneHour, sixHours, oneDay, threeDays, sevenDays
|
||||||
|
|
||||||
|
static func fromTimeInterval(_ ti: TimeInterval) -> Duration? {
|
||||||
|
for it in allCases where it.timeInterval == ti {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeInterval: TimeInterval {
|
||||||
|
switch self {
|
||||||
|
case .fiveMinutes:
|
||||||
|
return 5 * 60
|
||||||
|
case .thirtyMinutes:
|
||||||
|
return 30 * 60
|
||||||
|
case .oneHour:
|
||||||
|
return 60 * 60
|
||||||
|
case .sixHours:
|
||||||
|
return 6 * 60 * 60
|
||||||
|
case .oneDay:
|
||||||
|
return 24 * 60 * 60
|
||||||
|
case .threeDays:
|
||||||
|
return 3 * 24 * 60 * 60
|
||||||
|
case .sevenDays:
|
||||||
|
return 7 * 24 * 60 * 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComposePollOption: View {
|
||||||
|
@ObservedObject var poll: Draft.Poll
|
||||||
|
@ObservedObject var option: Draft.Poll.Option
|
||||||
|
let optionIndex: Int
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, borderWidth: 2)
|
||||||
|
.animation(.default)
|
||||||
|
|
||||||
|
|
||||||
|
textField
|
||||||
|
|
||||||
|
Button(action: self.removeOption) {
|
||||||
|
Image(systemName: "minus.circle.fill")
|
||||||
|
}
|
||||||
|
.foregroundColor(poll.options.count == 1 ? .gray : .red)
|
||||||
|
.disabled(poll.options.count == 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var textField: some View {
|
||||||
|
var field = ComposeEmojiTextField(text: $option.text, placeholder: "Option \(optionIndex + 1)")
|
||||||
|
return field.backgroundColor(.systemBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeOption() {
|
||||||
|
_ = withAnimation {
|
||||||
|
poll.options.remove(at: optionIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Checkbox: View {
|
||||||
|
private let radiusFraction: CGFloat
|
||||||
|
private let size: CGFloat = 20
|
||||||
|
private let innerSize: CGFloat
|
||||||
|
|
||||||
|
init(radiusFraction: CGFloat, borderWidth: CGFloat) {
|
||||||
|
self.radiusFraction = radiusFraction
|
||||||
|
self.innerSize = self.size - 2 * borderWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.cornerRadius(radiusFraction * size)
|
||||||
|
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color(UIColor.systemBackground))
|
||||||
|
.frame(width: innerSize, height: innerSize)
|
||||||
|
.cornerRadius(radiusFraction * innerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//struct ComposePollView_Previews: PreviewProvider {
|
||||||
|
// static var previews: some View {
|
||||||
|
// ComposePollView()
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -40,7 +40,7 @@ struct ComposeView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var postButtonEnabled: Bool {
|
var postButtonEnabled: Bool {
|
||||||
draft.hasContent && charactersRemaining >= 0 && !isPosting && !requiresAttachmentDescriptions
|
draft.hasContent && charactersRemaining >= 0 && !isPosting && !requiresAttachmentDescriptions && (draft.poll == nil || draft.poll!.options.allSatisfy { !$0.text.isEmpty })
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -101,7 +101,7 @@ struct ComposeView: View {
|
||||||
header
|
header
|
||||||
|
|
||||||
if draft.contentWarningEnabled {
|
if draft.contentWarningEnabled {
|
||||||
ComposeContentWarningTextField(text: $draft.contentWarning)
|
ComposeEmojiTextField(text: $draft.contentWarning, placeholder: "Write your warning here")
|
||||||
}
|
}
|
||||||
|
|
||||||
MainComposeTextView(
|
MainComposeTextView(
|
||||||
|
@ -109,6 +109,13 @@ struct ComposeView: View {
|
||||||
placeholder: Text("What's on your mind?")
|
placeholder: Text("What's on your mind?")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if let poll = draft.poll {
|
||||||
|
ComposePollView(draft: draft, poll: poll)
|
||||||
|
.transition(.opacity.combined(with: .asymmetric(insertion: .scale(scale: 0.5, anchor: .leading), removal: .scale(scale: 0.5, anchor: .trailing))))
|
||||||
|
.animation(.default)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ComposeAttachmentsList(
|
ComposeAttachmentsList(
|
||||||
draft: draft
|
draft: draft
|
||||||
)
|
)
|
||||||
|
@ -213,7 +220,10 @@ struct ComposeView: View {
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
spoilerText: contentWarning,
|
spoilerText: contentWarning,
|
||||||
visibility: draft.visibility,
|
visibility: draft.visibility,
|
||||||
language: nil)
|
language: nil,
|
||||||
|
pollOptions: draft.poll?.options.map(\.text),
|
||||||
|
pollExpiresIn: draft.poll == nil ? nil : Int(draft.poll!.duration),
|
||||||
|
pollMultiple: draft.poll?.multiple)
|
||||||
self.mastodonController.run(request) { (response) in
|
self.mastodonController.run(request) { (response) in
|
||||||
switch response {
|
switch response {
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
|
|
|
@ -114,7 +114,11 @@ class StatusPollView: UIView {
|
||||||
if expired {
|
if expired {
|
||||||
voteButton.setTitle("Expired", for: .disabled)
|
voteButton.setTitle("Expired", for: .disabled)
|
||||||
} else if poll.voted ?? false {
|
} else if poll.voted ?? false {
|
||||||
voteButton.setTitle("Voted", for: .disabled)
|
if status.account.id == mastodonController.account.id {
|
||||||
|
voteButton.setTitle("", for: .disabled)
|
||||||
|
} else {
|
||||||
|
voteButton.setTitle("Voted", for: .disabled)
|
||||||
|
}
|
||||||
} else if poll.multiple {
|
} else if poll.multiple {
|
||||||
voteButton.setTitle("Select multiple", for: .disabled)
|
voteButton.setTitle("Select multiple", for: .disabled)
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue