Posting posts & locales
This commit is contained in:
parent
c167717825
commit
64a71fc5ea
|
@ -2,6 +2,7 @@
|
|||
|
||||
import Foundation
|
||||
import RegexBuilder
|
||||
import SwiftUI
|
||||
|
||||
public protocol Endpoint: Sendable {
|
||||
func path() -> String
|
||||
|
@ -501,6 +502,38 @@ public struct StatusData: Encodable, Sendable {
|
|||
self.multiple = multiple
|
||||
self.expires_in = expires_in
|
||||
}
|
||||
|
||||
enum DefaultExpiry: Int, CaseIterable {
|
||||
case fiveMinutes = 300
|
||||
case thirtyMinutes = 1800
|
||||
case oneHour = 3600
|
||||
case sixHours = 21600
|
||||
case twelveHours = 43200
|
||||
case oneDay = 86400
|
||||
case threeDays = 259_200
|
||||
case sevenDays = 604_800
|
||||
|
||||
public var description: LocalizedStringKey {
|
||||
switch self {
|
||||
case .fiveMinutes:
|
||||
"poll.expiry.fiveMinutes"
|
||||
case .thirtyMinutes:
|
||||
"poll.expiry.thirtyMinutes"
|
||||
case .oneHour:
|
||||
"poll.expiry.oneHour"
|
||||
case .sixHours:
|
||||
"poll.expiry.sixHours"
|
||||
case .twelveHours:
|
||||
"poll.expiry.twelveHours"
|
||||
case .oneDay:
|
||||
"poll.expiry.oneDay"
|
||||
case .threeDays:
|
||||
"poll.expiry.threeDays"
|
||||
case .sevenDays:
|
||||
"poll.expiry.sevenDays"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct MediaAttribute: Encodable, Sendable {
|
||||
|
|
|
@ -463,6 +463,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"activity.poll.%@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "**%@**'s poll ended"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sondage de **%@** terminé"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"activity.reblogged.%@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
@ -1175,6 +1191,86 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.fiveMinutes" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "5 minutes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.oneDay" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "1 day"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.oneHour" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "1 hour"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.sevenDays" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "7 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.sixHours" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "6 hours"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.thirtyMinutes" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "30 minutes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.threeDays" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "3 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll.expiry.twelveHours" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "12 hours"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"posting.alt.apply" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
@ -2342,6 +2438,12 @@
|
|||
"state" : "translated",
|
||||
"value" : "Expired"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Expiré"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2352,6 +2454,12 @@
|
|||
"state" : "translated",
|
||||
"value" : "Expires %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Expire %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2362,6 +2470,12 @@
|
|||
"state" : "translated",
|
||||
"value" : "Hide Results"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cacher les votes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2372,6 +2486,12 @@
|
|||
"state" : "translated",
|
||||
"value" : "Show Results"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Voir les votes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2382,6 +2502,12 @@
|
|||
"state" : "translated",
|
||||
"value" : "Submit"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Envoyer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2410,6 +2536,30 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld votant"
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld votants"
|
||||
}
|
||||
},
|
||||
"zero" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aucun votants"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2477,6 +2627,52 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"status.posting.poll.disable-multi" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Multiple votes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.posting.poll.enable-multi" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "One vote"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.posting.poll.expiry" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Poll Duration"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.posting.poll.option-%lld" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Option %lld"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Option %lld"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.posting.post" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
|
|
@ -25,6 +25,12 @@ struct PostingView: View {
|
|||
@State private var selectedPhotos: [PhotosPickerItem] = []
|
||||
@State private var player: AVPlayer?
|
||||
|
||||
@State private var hasPoll: Bool = false
|
||||
@State private var pollOptions: [String] = ["", ""]
|
||||
@State private var pollExpiry: StatusData.PollData.DefaultExpiry = .oneDay
|
||||
@State private var multiSelect: Bool = false
|
||||
|
||||
|
||||
@State private var selectingEmoji: Bool = false
|
||||
@State private var makingAlt: MediaContainer? = nil
|
||||
|
||||
|
@ -87,6 +93,10 @@ struct PostingView: View {
|
|||
mediasView(containers: mediaContainers)
|
||||
}
|
||||
|
||||
if hasPoll {
|
||||
editPollView
|
||||
}
|
||||
|
||||
editorButtons
|
||||
.padding(.vertical)
|
||||
}
|
||||
|
@ -182,7 +192,12 @@ struct PostingView: View {
|
|||
// await upload(container: container)
|
||||
// }
|
||||
|
||||
let json: StatusData = .init(status: viewModel.postText.string, visibility: visibility, inReplyToId: replyId, mediaIds: mediaContainers.compactMap { $0.mediaAttachment?.id }, mediaAttributes: mediaAttributes)
|
||||
var pollData: StatusData.PollData? = nil
|
||||
if self.hasPoll {
|
||||
pollData = StatusData.PollData(options: self.pollOptions, multiple: self.multiSelect, expires_in: pollExpiry.rawValue)
|
||||
}
|
||||
|
||||
let json: StatusData = .init(status: viewModel.postText.string, visibility: visibility, inReplyToId: replyId, mediaIds: mediaContainers.compactMap { $0.mediaAttachment?.id }, poll: pollData, mediaAttributes: mediaAttributes)
|
||||
|
||||
let isEdit: Bool = editId != nil
|
||||
let endp: Endpoint = isEdit ? Statuses.editStatus(id: editId!, json: json) : Statuses.postStatus(json: json)
|
||||
|
@ -200,6 +215,77 @@ struct PostingView: View {
|
|||
}
|
||||
}
|
||||
|
||||
var editPollView: some View {
|
||||
VStack {
|
||||
ForEach(0 ..< pollOptions.count, id: \.self) { i in
|
||||
let isLast: Bool = pollOptions.count - 1 == i;
|
||||
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.stroke(Color.gray.opacity(0.4), lineWidth: 2.5)
|
||||
.frame(width: 300, height: 50)
|
||||
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||
.overlay {
|
||||
HStack {
|
||||
TextField("status.posting.poll.option-\(i + 1)", text: $pollOptions[i], axis: .horizontal)
|
||||
.font(.subheadline)
|
||||
.padding(.leading, 25)
|
||||
.foregroundStyle(Color(uiColor: UIColor.label))
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 3.5) {
|
||||
Button {
|
||||
withAnimation(.spring) {
|
||||
self.pollOptions.append("")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.callout)
|
||||
.foregroundStyle(pollOptions.count >= 4 || !isLast ? Color.gray : Color(uiColor: UIColor.label))
|
||||
}
|
||||
.disabled(pollOptions.count >= 4 || !isLast)
|
||||
|
||||
Button {
|
||||
withAnimation(.spring) {
|
||||
if pollOptions.count == 2 {
|
||||
self.hasPoll = false
|
||||
} else {
|
||||
let index: Int = i
|
||||
self.pollOptions.remove(at: index)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "minus.circle.fill")
|
||||
.font(.callout)
|
||||
.foregroundStyle(Color(uiColor: UIColor.label))
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 25)
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
withAnimation(.spring) {
|
||||
multiSelect.toggle()
|
||||
}
|
||||
} label: {
|
||||
Text(multiSelect ? LocalizedStringKey("status.posting.poll.disable-multi") : LocalizedStringKey("status.posting.poll.enable-multi"))
|
||||
}
|
||||
.buttonStyle(LargeButton(filled: false, height: 7.5))
|
||||
|
||||
Spacer()
|
||||
|
||||
Picker("status.posting.poll.expiry", selection: $pollExpiry) {
|
||||
ForEach(StatusData.PollData.DefaultExpiry.allCases, id: \.self) { expiry in
|
||||
Text(expiry.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: 300)
|
||||
}
|
||||
}
|
||||
|
||||
var loading: some View {
|
||||
ProgressView()
|
||||
.foregroundStyle(.white)
|
||||
|
@ -385,39 +471,51 @@ struct PostingView: View {
|
|||
}
|
||||
|
||||
var editorButtons: some View {
|
||||
//MARK: Action buttons
|
||||
HStack(spacing: 18) {
|
||||
actionButton("photo.badge.plus") {
|
||||
selectingPhotos.toggle()
|
||||
}
|
||||
.photosPicker(isPresented: $selectingPhotos, selection: $selectedPhotos, maxSelectionCount: 4, matching: .any(of: [.images, .videos]), photoLibrary: .shared())
|
||||
.onChange(of: selectedPhotos) { oldValue, _ in
|
||||
if selectedPhotos.count > 4 {
|
||||
selectedPhotos = selectedPhotos.prefix(4).map { $0 }
|
||||
if !self.hasPoll {
|
||||
actionButton("photo.badge.plus") {
|
||||
selectingPhotos.toggle()
|
||||
}
|
||||
|
||||
let removedIDs = oldValue
|
||||
.filter { !selectedPhotos.contains($0) }
|
||||
.compactMap(\.itemIdentifier)
|
||||
mediaContainers.removeAll { removedIDs.contains($0.id) }
|
||||
|
||||
let newPickerItems = selectedPhotos.filter { !oldValue.contains($0) }
|
||||
if !newPickerItems.isEmpty {
|
||||
loadingContent = true
|
||||
Task {
|
||||
for item in newPickerItems {
|
||||
initImage(for: item)
|
||||
.transition(.opacity.combined(with: .move(edge: .leading)))
|
||||
.photosPicker(isPresented: $selectingPhotos, selection: $selectedPhotos, maxSelectionCount: 4, matching: .any(of: [.images, .videos]), photoLibrary: .shared())
|
||||
.onChange(of: selectedPhotos) { oldValue, _ in
|
||||
if selectedPhotos.count > 4 {
|
||||
selectedPhotos = selectedPhotos.prefix(4).map { $0 }
|
||||
}
|
||||
|
||||
let removedIDs = oldValue
|
||||
.filter { !selectedPhotos.contains($0) }
|
||||
.compactMap(\.itemIdentifier)
|
||||
mediaContainers.removeAll { removedIDs.contains($0.id) }
|
||||
|
||||
let newPickerItems = selectedPhotos.filter { !oldValue.contains($0) }
|
||||
if !newPickerItems.isEmpty {
|
||||
loadingContent = true
|
||||
Task {
|
||||
for item in newPickerItems {
|
||||
initImage(for: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tint(Color.blue)
|
||||
}
|
||||
.tint(Color.blue)
|
||||
|
||||
actionButton("number") {
|
||||
DispatchQueue.main.async {
|
||||
viewModel.append(text: "#")
|
||||
if mediaContainers.isEmpty || selectedPhotos.isEmpty {
|
||||
actionButton("checklist") {
|
||||
withAnimation(.spring) {
|
||||
self.hasPoll.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// actionButton("number") {
|
||||
// DispatchQueue.main.async {
|
||||
// viewModel.append(text: "#")
|
||||
// }
|
||||
// }
|
||||
|
||||
let smileSf = colorScheme == .light ? "face.smiling" : "face.smiling.inverse"
|
||||
actionButton(smileSf) {
|
||||
viewModel.textView?.resignFirstResponder()
|
||||
|
|
Loading…
Reference in New Issue