commit 8c8084df6f9fa981b448795237ee9c2ab18b2714
parent 8550af403b49a80aab0324eecd441376b341fe97
Author: triesap <triesap@radroots.dev>
Date: Sat, 4 Oct 2025 22:12:04 +0100
Add `PostView` for composing and posting text notes.
Diffstat:
3 files changed, 116 insertions(+), 0 deletions(-)
diff --git a/Radroots.xcodeproj/project.pbxproj b/Radroots.xcodeproj/project.pbxproj
@@ -13,6 +13,7 @@
2B3886FD26434A54F3726591 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D19AA8D515FC8F5D2407378 /* Localizable.strings */; };
2B6ACA26689B355CECBFFB57 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16A7641E5C643B4B36CFEDA8 /* SetupView.swift */; };
360F23EFE80FDBDC6983FB15 /* AppEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D448C9655B708CA3FA8712B9 /* AppEntry.swift */; };
+ 53694F65BFDDA2B5213063EE /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BADD960784613FCC0EA1D14 /* PostView.swift */; };
7FD8FB018DA09568303194B2 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE61264E2C98E73828E8680 /* Strings.swift */; };
A1B921027DA7ACD7343BE250 /* SectionWideButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17CA8F5611075F60F214A00 /* SectionWideButton.swift */; };
B971351ABE8E79A472B4DC7D /* View+Nav.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0B0C9861CD86EAD3CAD549E /* View+Nav.swift */; };
@@ -27,6 +28,7 @@
/* Begin PBXFileReference section */
08FA88664E5E3ED3A24D56CC /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
0A0274A0260D1C04F40C71AF /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
+ 0BADD960784613FCC0EA1D14 /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
138AA7BAA021EE13E829390B /* Bundle+Build.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Build.swift"; sourceTree = "<group>"; };
16A7641E5C643B4B36CFEDA8 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
2818363B157125491FB84A1E /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
@@ -145,6 +147,7 @@
isa = PBXGroup;
children = (
0A0274A0260D1C04F40C71AF /* HomeView.swift */,
+ 0BADD960784613FCC0EA1D14 /* PostView.swift */,
C71A93F98C7B93188748B99B /* ProfileView.swift */,
C1D9496F9F05A4E79E73A247 /* RelaysView.swift */,
E1D12A016D1377CDFBFB0F9B /* SettingsView.swift */,
@@ -271,6 +274,7 @@
E3864E34D67BAD0744B93180 /* Bundle+Build.swift in Sources */,
1E5B41A3E1F9A7D68F63B079 /* HomeView.swift in Sources */,
C512BD267A3E2B8F10FABB3B /* Logger.swift in Sources */,
+ 53694F65BFDDA2B5213063EE /* PostView.swift in Sources */,
275D4D574BF3B3C1DD746CE7 /* ProfileView.swift in Sources */,
022DA21729F49893319717AA /* RelaysView.swift in Sources */,
A1B921027DA7ACD7343BE250 /* SectionWideButton.swift in Sources */,
diff --git a/Radroots/Views/HomeView.swift b/Radroots/Views/HomeView.swift
@@ -31,6 +31,14 @@ private struct HomeDashboardView: View {
var body: some View {
List {
+ Section("Compose") {
+ NavigationLink {
+ PostView()
+ } label: {
+ Label("New Post", systemImage: "square.and.pencil")
+ }
+ }
+
Section("Your Identity") {
NavigationLink {
ProfileView()
diff --git a/Radroots/Views/PostView.swift b/Radroots/Views/PostView.swift
@@ -0,0 +1,104 @@
+import SwiftUI
+import RadrootsKit
+
+struct PostView: View {
+ @EnvironmentObject private var app: AppState
+ @State private var text: String = ""
+ @State private var isPosting = false
+ @State private var resultMessage: String?
+ @State private var showResult = false
+ @FocusState private var focused: Bool
+
+ private var isConnected: Bool { app.relayConnectedCount > 0 }
+ private var canPost: Bool {
+ isConnected && !isPosting && !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+ }
+
+ var body: some View {
+ Form {
+ Section {
+ ZStack(alignment: .topLeading) {
+ TextEditor(text: $text)
+ .frame(minHeight: 160)
+ .focused($focused)
+ .submitLabel(.send)
+ if text.isEmpty {
+ Text("What's happening?")
+ .foregroundStyle(.secondary)
+ .padding(.top, 8)
+ .padding(.leading, 5)
+ .allowsHitTesting(false)
+ }
+ }
+ HStack {
+ Text("\(text.count)")
+ .font(.footnote)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Button {
+ post()
+ } label: {
+ if isPosting {
+ ProgressView()
+ } else {
+ Text("Post")
+ .fontWeight(.semibold)
+ }
+ }
+ .buttonStyle(.borderedProminent)
+ .disabled(!canPost)
+ }
+ } footer: {
+ VStack(alignment: .leading, spacing: 4) {
+ if !isConnected {
+ Text("No relays connected. Configure and connect to post.")
+ .foregroundStyle(.red)
+ }
+ if let e = app.relayLastError {
+ Text(e).foregroundStyle(.secondary)
+ }
+ }
+ }
+ }
+ .listStyle(.insetGrouped)
+ .inlineNavigationTitle("Compose")
+ .toolbar {
+ ToolbarItemGroup(placement: .keyboard) {
+ Spacer()
+ Button("Done") { focused = false }
+ }
+ }
+ .alert("Post Result", isPresented: $showResult) {
+ Button("OK", role: .cancel) { }
+ } message: {
+ Text(resultMessage ?? "")
+ }
+ .onAppear { focused = true }
+ }
+
+ private func post() {
+ guard let rt = app.radroots.runtime else { return }
+ let content = text.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !content.isEmpty else { return }
+ isPosting = true
+ resultMessage = nil
+ Task {
+ do {
+ let id = try rt.nostrPostTextNote(content: content)
+ await MainActor.run {
+ resultMessage = "Posted kind:1 event: \(id)"
+ showResult = true
+ text = ""
+ isPosting = false
+ app.refresh()
+ }
+ } catch {
+ await MainActor.run {
+ resultMessage = "Failed to post: \(error)"
+ showResult = true
+ isPosting = false
+ }
+ }
+ }
+ }
+}