This commit is contained in:
lumaa-dev 2024-08-31 22:41:14 +02:00
parent e787c8798a
commit e679ee11c6
7 changed files with 261 additions and 29 deletions

View File

@ -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 */,

View File

@ -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)
}

View File

@ -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?)

View File

@ -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" : {

View File

@ -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])
}
}

View File

@ -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")
}
}
}
}

View File

@ -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 {