Drafts
This commit is contained in:
parent
e787c8798a
commit
e679ee11c6
|
@ -9,6 +9,10 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
B9029FC22B81259400AA9B68 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = B9029FC12B81259400AA9B68 /* Secret.plist */; };
|
||||
B9029FC42B8125CE00AA9B68 /* HuggingFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9029FC32B8125CE00AA9B68 /* HuggingFace.swift */; };
|
||||
B90DEB1F2C822C2700D06121 /* StatusDraft.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90DEB1E2C822C2700D06121 /* StatusDraft.swift */; };
|
||||
B90DEB202C822C2700D06121 /* StatusDraft.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90DEB1E2C822C2700D06121 /* StatusDraft.swift */; };
|
||||
B90DEB222C822ED400D06121 /* PostDraftView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90DEB212C822ED400D06121 /* PostDraftView.swift */; };
|
||||
B90DEB232C822ED400D06121 /* PostDraftView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90DEB212C822ED400D06121 /* PostDraftView.swift */; };
|
||||
B915C4422B6F908C00042DDB /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B915C4412B6F908C00042DDB /* ProfileView.swift */; };
|
||||
B93126EA2C29C63100BF16E9 /* ContentFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93126E92C29C63000BF16E9 /* ContentFilter.swift */; };
|
||||
B93126F02C2AEB8300BF16E9 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93126EF2C2AEB8300BF16E9 /* FilterView.swift */; };
|
||||
|
@ -261,6 +265,8 @@
|
|||
/* Begin PBXFileReference section */
|
||||
B9029FC12B81259400AA9B68 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Secret.plist; sourceTree = "<group>"; };
|
||||
B9029FC32B8125CE00AA9B68 /* HuggingFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFace.swift; sourceTree = "<group>"; };
|
||||
B90DEB1E2C822C2700D06121 /* StatusDraft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusDraft.swift; sourceTree = "<group>"; };
|
||||
B90DEB212C822ED400D06121 /* PostDraftView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostDraftView.swift; sourceTree = "<group>"; };
|
||||
B915C4412B6F908C00042DDB /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
B93126E92C29C63000BF16E9 /* ContentFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilter.swift; sourceTree = "<group>"; };
|
||||
B93126EF2C2AEB8300BF16E9 /* FilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterView.swift; sourceTree = "<group>"; };
|
||||
|
@ -433,6 +439,7 @@
|
|||
B98F47992B653CAE0092000F /* Compressor.swift */,
|
||||
B9BCC3172B90B3BC00211976 /* Tenor.swift */,
|
||||
B93126E92C29C63000BF16E9 /* ContentFilter.swift */,
|
||||
B90DEB1E2C822C2700D06121 /* StatusDraft.swift */,
|
||||
);
|
||||
path = Content;
|
||||
sourceTree = "<group>";
|
||||
|
@ -576,6 +583,7 @@
|
|||
B97BCE252B3DE5A10044756D /* AccountView.swift */,
|
||||
B98BC7462B46CE6300595441 /* PostDetailsView.swift */,
|
||||
B93B677B2B433A6E000892E9 /* PostingView.swift */,
|
||||
B90DEB212C822ED400D06121 /* PostDraftView.swift */,
|
||||
B9F8FA152B5D3AC30044DAB4 /* SafariView.swift */,
|
||||
B9A8DAB92BB7364300A890CC /* PostsView.swift */,
|
||||
B9A80DD92C66DE1000DE3D88 /* ReportStatusView.swift */,
|
||||
|
@ -831,6 +839,8 @@
|
|||
B9C20D122B921C78004DC9B3 /* FollowCountWidget.swift in Sources */,
|
||||
B9A80DDE2C67BFF800DE3D88 /* CreatePostWidget.swift in Sources */,
|
||||
B9C20D102B921C78004DC9B3 /* ThreadedWidgetsBundle.swift in Sources */,
|
||||
B90DEB1F2C822C2700D06121 /* StatusDraft.swift in Sources */,
|
||||
B90DEB232C822ED400D06121 /* PostDraftView.swift in Sources */,
|
||||
B9A80DE02C67C2D000DE3D88 /* Navigator.swift in Sources */,
|
||||
B9C20D142B921C78004DC9B3 /* AppIntent.swift in Sources */,
|
||||
B9C20D372B9229EC004DC9B3 /* AppInfo.swift in Sources */,
|
||||
|
@ -994,6 +1004,8 @@
|
|||
B9B63B272B449CDC00BBC82D /* SearchResults.swift in Sources */,
|
||||
B9B63B252B44997400BBC82D /* QuotePostView.swift in Sources */,
|
||||
B9BCC3182B90B3BC00211976 /* Tenor.swift in Sources */,
|
||||
B90DEB202C822C2700D06121 /* StatusDraft.swift in Sources */,
|
||||
B90DEB222C822ED400D06121 /* PostDraftView.swift in Sources */,
|
||||
B9B469DB2B9B2EDB00AD5585 /* ComingSoonView.swift in Sources */,
|
||||
B97BCE242B3DD8400044756D /* HapticManager.swift in Sources */,
|
||||
B9B63B212B442D1500BBC82D /* DynamicTextEditor.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Made by Lumaa
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
class StatusDraft {
|
||||
var content: String
|
||||
var visibility: Visibility
|
||||
|
||||
var hasPoll: Bool
|
||||
var pollOptions: [String]
|
||||
var pollMulti: Bool
|
||||
var pollExpire: Int
|
||||
|
||||
init(
|
||||
content: String,
|
||||
visibility: Visibility,
|
||||
hasPoll: Bool = false,
|
||||
pollOptions: [String] = [],
|
||||
pollMulti: Bool = false,
|
||||
pollExpire: StatusData.PollData.DefaultExpiry = .oneDay
|
||||
) {
|
||||
self.content = content
|
||||
self.visibility = visibility
|
||||
self.hasPoll = hasPoll
|
||||
self.pollOptions = pollOptions
|
||||
self.pollMulti = pollMulti
|
||||
self.pollExpire = pollExpire.rawValue
|
||||
}
|
||||
|
||||
func setPoll(options: [String], multiselect: Bool, expiresIn: StatusData.PollData.DefaultExpiry = .oneDay) {
|
||||
self.setPoll(options: options, multiselect: multiselect, expiresIn: expiresIn.rawValue)
|
||||
}
|
||||
|
||||
func setPoll(options: [String], multiselect: Bool, expiresIn: Int = StatusData.PollData.DefaultExpiry.oneDay.rawValue) {
|
||||
self.pollOptions = options
|
||||
self.pollMulti = multiselect
|
||||
self.pollExpire = expiresIn
|
||||
}
|
||||
|
||||
static let empty: StatusDraft = .init(content: "", visibility: .pub)
|
||||
}
|
|
@ -605,6 +605,12 @@ public struct StatusData: Encodable, Sendable {
|
|||
}
|
||||
}
|
||||
|
||||
extension StatusData.PollData.DefaultExpiry {
|
||||
static func getFromInt(_ time: Int) -> Self? {
|
||||
return Self.allCases.filter({ $0.rawValue == time }).first
|
||||
}
|
||||
}
|
||||
|
||||
public enum Trends: Endpoint {
|
||||
case tags
|
||||
case statuses(offset: Int?)
|
||||
|
|
|
@ -3370,6 +3370,46 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"status.drafts.add" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add to Drafts"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.drafts.empty" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "You have no drafts"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.drafts.open" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Open Drafts"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.drafts.plus" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Subscribe to Threaded+ to have more drafts"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status.editing" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
|
|
@ -48,6 +48,6 @@ public extension View {
|
|||
@ViewBuilder
|
||||
func modelData() -> some View {
|
||||
self
|
||||
.modelContainer(for: [LoggedAccount.self, ModelFilter.self])
|
||||
.modelContainer(for: [LoggedAccount.self, ModelFilter.self, StatusDraft.self])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Made by Lumaa
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct PostDraftView: View {
|
||||
@Environment(AccountManager.self) private var accountManager: AccountManager
|
||||
@Environment(\.dismiss) private var dismiss: DismissAction
|
||||
|
||||
@Query private var drafts: [StatusDraft]
|
||||
|
||||
@Binding var selectedDraft: StatusDraft?
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
if drafts.count > 0 {
|
||||
List {
|
||||
ForEach(drafts, id: \.self) { draft in
|
||||
Button {
|
||||
selectedDraft = draft
|
||||
dismiss()
|
||||
} label: {
|
||||
VStack(alignment: .leading) {
|
||||
TextEmoji(HTMLString(stringValue: draft.content), emojis: accountManager.forceAccount().emojis)
|
||||
.lineLimit(3, reservesSpace: true)
|
||||
.font(.callout)
|
||||
|
||||
// Label("status.drafts.attachments-\(draft.attachments.count)", systemImage: draft.attachments.count > 1 ? "photo.on.rectangle.angled" : "photo")
|
||||
// .multilineTextAlignment(.leading)
|
||||
// .font(.caption)
|
||||
// .foregroundStyle(Color.gray)
|
||||
// .lineLimit(1, reservesSpace: false)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(7.5)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.stroke(.gray.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
.listRowThreaded()
|
||||
}
|
||||
}
|
||||
.listThreaded()
|
||||
} else {
|
||||
ContentUnavailableView("status.drafts.empty", systemImage: "plus.circle.dashed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
//Made by Lumaa
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import UIKit
|
||||
import PhotosUI
|
||||
|
||||
struct PostingView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.modelContext) private var modelContext: ModelContext
|
||||
@Environment(\.dismiss) private var dismiss: DismissAction
|
||||
@Environment(\.colorScheme) private var colorScheme: ColorScheme
|
||||
@Environment(AccountManager.self) private var accountManager: AccountManager
|
||||
@Environment(AppDelegate.self) private var appDelegate: AppDelegate
|
||||
|
||||
|
||||
@Query private var drafts: [StatusDraft]
|
||||
|
||||
public var initialString: String = ""
|
||||
public var replyId: String? = nil
|
||||
public var editId: String? = nil
|
||||
|
@ -30,11 +34,13 @@ struct PostingView: View {
|
|||
@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
|
||||
|
||||
|
||||
@State private var selectingDrafts: Bool = false
|
||||
@State private var selectedDraft: StatusDraft? = nil
|
||||
|
||||
@State private var loadingContent: Bool = false
|
||||
@State private var postingStatus: Bool = false
|
||||
|
||||
|
@ -43,7 +49,24 @@ struct PostingView: View {
|
|||
self.replyId = replyId
|
||||
self.editId = editId
|
||||
}
|
||||
|
||||
|
||||
private func fromDraft(_ draft: StatusDraft) {
|
||||
self.viewModel.postText = .init(string: draft.content)
|
||||
self.viewModel.formatText()
|
||||
|
||||
if draft.hasPoll {
|
||||
self.hasPoll = true
|
||||
self.pollOptions = draft.pollOptions
|
||||
self.multiSelect = draft.pollMulti
|
||||
self.pollExpiry = StatusData.PollData.DefaultExpiry.getFromInt(draft.pollExpire) ?? .oneDay
|
||||
} else {
|
||||
self.hasPoll = false
|
||||
self.pollOptions = ["", ""]
|
||||
self.multiSelect = false
|
||||
self.pollExpiry = .oneDay
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if accountManager.getAccount() != nil {
|
||||
posting
|
||||
|
@ -59,6 +82,13 @@ struct PostingView: View {
|
|||
.presentationDetents([.height(235), .medium])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.sheet(isPresented: $selectingDrafts) {
|
||||
if let selected = selectedDraft {
|
||||
self.fromDraft(selected)
|
||||
}
|
||||
} content: {
|
||||
PostDraftView(selectedDraft: $selectedDraft)
|
||||
}
|
||||
} else {
|
||||
loading
|
||||
.background(Color.appBackground)
|
||||
|
@ -113,27 +143,29 @@ struct PostingView: View {
|
|||
.scrollIndicators(.hidden)
|
||||
.frame(maxHeight: appDelegate.windowHeight - 140)
|
||||
.safeAreaInset(edge: .bottom, alignment: .leading) {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
postText()
|
||||
} label: {
|
||||
if postingStatus {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.foregroundStyle(Color.appBackground)
|
||||
.tint(Color.appBackground)
|
||||
} else {
|
||||
Text("status.posting.post")
|
||||
}
|
||||
}
|
||||
.disabled(postingStatus || viewModel.postText.length <= 0)
|
||||
.buttonStyle(LargeButton(filled: true, height: 7.5, disabled: postingStatus || viewModel.postText.length <= 0))
|
||||
}
|
||||
|
||||
//MARK: Buttons below
|
||||
HStack(alignment: .center) {
|
||||
editorButtons
|
||||
|
||||
Spacer()
|
||||
|
||||
postButtons
|
||||
.padding(.horizontal, 18)
|
||||
|
||||
Button {
|
||||
postText()
|
||||
} label: {
|
||||
if postingStatus {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.foregroundStyle(Color.appBackground)
|
||||
.tint(Color.appBackground)
|
||||
} else {
|
||||
Text("status.posting.post")
|
||||
}
|
||||
}
|
||||
.disabled(postingStatus || viewModel.postText.length <= 0)
|
||||
.buttonStyle(LargeButton(filled: true, height: 7.5, disabled: postingStatus || viewModel.postText.length <= 0))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
@ -538,7 +570,56 @@ struct PostingView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var postButtons: some View {
|
||||
//MARK: Post buttons
|
||||
HStack(spacing: 18) {
|
||||
actionMenu("plus.square.dashed") {
|
||||
let addDisabled: Bool = self.drafts.count >= 3 && !AppDelegate.hasPlus()
|
||||
|
||||
Button {
|
||||
selectingDrafts.toggle()
|
||||
} label: {
|
||||
Label("status.drafts.open", systemImage: "pencil.and.scribble")
|
||||
}
|
||||
|
||||
if addDisabled {
|
||||
Divider()
|
||||
}
|
||||
|
||||
Button {
|
||||
let newDraft: StatusDraft = .init(
|
||||
content: viewModel.postText.string,
|
||||
visibility: visibility
|
||||
)
|
||||
|
||||
modelContext.insert(newDraft) // save draft
|
||||
self.fromDraft(.empty) // empty the current view
|
||||
|
||||
HapticManager.playHaptics(haptics: Haptic.success)
|
||||
} label: {
|
||||
Label("status.drafts.add", systemImage: "plus.circle.dashed")
|
||||
}
|
||||
.disabled(addDisabled || viewModel.postText.string.isEmpty)
|
||||
|
||||
if addDisabled {
|
||||
Text("status.drafts.plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func actionMenu(_ image: String, @ViewBuilder menu: () -> some View) -> some View {
|
||||
Menu {
|
||||
menu()
|
||||
} label: {
|
||||
Image(systemName: image)
|
||||
.font(.callout)
|
||||
}
|
||||
.tint(Color.gray)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func actionButton(_ image: String, action: @escaping () -> Void) -> some View {
|
||||
Button {
|
||||
|
|
Loading…
Reference in New Issue