mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-25 09:41:02 +00:00
Post editor: Drafts support
This commit is contained in:
parent
f1219d319b
commit
9cf863d8c3
6 changed files with 94 additions and 59 deletions
|
@ -48,27 +48,6 @@ struct PushNotificationsView: View {
|
|||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
|
||||
Section {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Auth key:")
|
||||
Text(pushNotifications.notificationsAuthKeyAsKey.base64EncodedString())
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text("Public key:")
|
||||
Text(pushNotifications.notificationsPrivateKeyAsKey.publicKey.x963Representation.base64EncodedString())
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
} header: {
|
||||
Text("Keys information")
|
||||
} footer: {
|
||||
Text("Your notifications are sent through a proxy server and are encrypted using a public/private key pair that is stored only on your device. The public key is sent to the server, so it can encrypt your notifications so that only your device can decrypt them.")
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
||||
}
|
||||
.navigationTitle("Push Notifications")
|
||||
.scrollContentBackground(.hidden)
|
||||
|
|
|
@ -21,7 +21,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
|
||||
guard let encodedPayload = bestAttemptContent.userInfo["m"] as? String,
|
||||
let payload = Data(base64Encoded: encodedPayload.URLSafeBase64ToBase64()) else {
|
||||
bestAttemptContent.title = "Failied to decode payload as base 64"
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
}
|
||||
|
@ -29,14 +28,12 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
guard let encodedPublicKey = bestAttemptContent.userInfo["k"] as? String,
|
||||
let publicKeyData = Data(base64Encoded: encodedPublicKey.URLSafeBase64ToBase64()),
|
||||
let publicKey = try? P256.KeyAgreement.PublicKey(x963Representation: publicKeyData) else {
|
||||
bestAttemptContent.title = "Failied to inflate public key"
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
}
|
||||
|
||||
guard let encodedSalt = bestAttemptContent.userInfo["s"] as? String,
|
||||
let salt = Data(base64Encoded: encodedSalt.URLSafeBase64ToBase64()) else {
|
||||
bestAttemptContent.title = "Failied to inflate salt"
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
}
|
||||
|
@ -47,7 +44,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
privateKey: privateKey,
|
||||
publicKey: publicKey),
|
||||
let notification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintextData) else {
|
||||
bestAttemptContent.title = "Failied to JSON decode the Notification"
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ public class UserPreferences: ObservableObject {
|
|||
|
||||
@AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = []
|
||||
@AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari
|
||||
@AppStorage("draft_posts") public var draftsPosts: [String] = []
|
||||
|
||||
public var pushNotificationsCount: Int {
|
||||
get {
|
||||
|
|
|
@ -5,12 +5,15 @@ import Models
|
|||
import Env
|
||||
|
||||
struct StatusEditorAccessoryView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@EnvironmentObject private var preferences: UserPreferences
|
||||
@EnvironmentObject private var theme: Theme
|
||||
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||
|
||||
@FocusState<Bool>.Binding var isSpoilerTextFocused: Bool
|
||||
@ObservedObject var viewModel: StatusEditorViewModel
|
||||
@State private var isPrivacySheetDisplayed: Bool = false
|
||||
@State private var isDrafsSheetDisplayed: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
|
@ -42,7 +45,12 @@ struct StatusEditorAccessoryView: View {
|
|||
Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill": "exclamationmark.triangle")
|
||||
}
|
||||
|
||||
visibilityMenu
|
||||
Button {
|
||||
isDrafsSheetDisplayed = true
|
||||
} label: {
|
||||
Image(systemName: "archivebox")
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
|
@ -53,6 +61,40 @@ struct StatusEditorAccessoryView: View {
|
|||
.padding(.vertical, 12)
|
||||
.background(.ultraThinMaterial)
|
||||
}
|
||||
.sheet(isPresented: $isDrafsSheetDisplayed) {
|
||||
draftsSheetView
|
||||
}
|
||||
}
|
||||
|
||||
private var draftsSheetView: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
ForEach(preferences.draftsPosts, id: \.self) { draft in
|
||||
Text(draft)
|
||||
.lineLimit(3)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.onTapGesture {
|
||||
viewModel.insertStatusText(text: draft)
|
||||
isDrafsSheetDisplayed = false
|
||||
}
|
||||
}
|
||||
.onDelete { indexes in
|
||||
if let index = indexes.first {
|
||||
preferences.draftsPosts.remove(at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel", action: { dismiss() })
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.navigationTitle("Drafts")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.presentationDetents([.medium])
|
||||
}
|
||||
|
||||
|
||||
|
@ -61,30 +103,4 @@ struct StatusEditorAccessoryView: View {
|
|||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
|
||||
private var visibilityMenu: some View {
|
||||
Button {
|
||||
isPrivacySheetDisplayed = true
|
||||
} label: {
|
||||
Image(systemName: viewModel.visibility.iconName)
|
||||
}
|
||||
.sheet(isPresented: $isPrivacySheetDisplayed) {
|
||||
Form {
|
||||
Section("Post visibility") {
|
||||
ForEach(Models.Visibility.allCases, id: \.self) { visibility in
|
||||
Button {
|
||||
viewModel.visibility = visibility
|
||||
isPrivacySheetDisplayed = false
|
||||
} label: {
|
||||
Label(visibility.title, systemImage: visibility.iconName)
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
}
|
||||
.presentationDetents([.height(300)])
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import PhotosUI
|
|||
import NukeUI
|
||||
|
||||
public struct StatusEditorView: View {
|
||||
@EnvironmentObject private var preferences: UserPreferences
|
||||
@EnvironmentObject private var theme: Theme
|
||||
@EnvironmentObject private var client: Client
|
||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||
|
@ -17,6 +18,8 @@ public struct StatusEditorView: View {
|
|||
@StateObject private var viewModel: StatusEditorViewModel
|
||||
@FocusState private var isSpoilerTextFocused: Bool
|
||||
|
||||
@State private var isDismissAlertPresented: Bool = false
|
||||
|
||||
public init(mode: StatusEditorViewModel.Mode) {
|
||||
_viewModel = StateObject(wrappedValue: .init(mode: mode))
|
||||
}
|
||||
|
@ -88,13 +91,30 @@ public struct StatusEditorView: View {
|
|||
}
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
if !viewModel.statusText.string.isEmpty {
|
||||
isDismissAlertPresented = true
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog("",
|
||||
isPresented: $isDismissAlertPresented,
|
||||
actions: {
|
||||
Button("Delete Draft", role: .destructive) {
|
||||
dismiss()
|
||||
}
|
||||
Button("Save Draft") {
|
||||
preferences.draftsPosts.insert(viewModel.statusText.string, at: 0)
|
||||
dismiss()
|
||||
}
|
||||
Button("Cancel", role: .cancel) { }
|
||||
})
|
||||
.interactiveDismissDisabled(!viewModel.statusText.string.isEmpty)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -116,10 +136,8 @@ public struct StatusEditorView: View {
|
|||
if let account = currentAccount.account {
|
||||
HStack {
|
||||
AvatarView(url: account.avatar, size: .status)
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
account.displayNameWithEmojis
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
privacyMenu
|
||||
Text("@\(account.acct)")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
|
@ -128,4 +146,29 @@ public struct StatusEditorView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var privacyMenu: some View {
|
||||
Menu {
|
||||
Section("Post visibility") {
|
||||
ForEach(Models.Visibility.allCases, id: \.self) { visibility in
|
||||
Button {
|
||||
viewModel.visibility = visibility
|
||||
} label: {
|
||||
Label(visibility.title, systemImage: visibility.iconName)
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Label(viewModel.visibility.title, systemImage: viewModel.visibility.iconName)
|
||||
Image(systemName: "chevron.down")
|
||||
}
|
||||
.font(.footnote)
|
||||
.padding(4)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(theme.tintColor, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ extension Visibility {
|
|||
case .priv:
|
||||
return "Followers"
|
||||
case .direct:
|
||||
return "Private Mention"
|
||||
return "Private"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue