field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | LICENSE

commit cc77c9c37607b041facc0bca13d1b6b9f6231325
parent 29565094868a88a103526a7275bb816bb63c6f62
Author: triesap <tyson@radroots.org>
Date:   Fri, 19 Jun 2026 16:56:43 -0700

api: drop retired trade mobile consumers

- remove Swift wrappers for retired trade validation, order, and message APIs.
- simplify listing detail around the active listing read model.
- delete the retired order request view from the app target.
- lock FFI generation to the reduced field_lib trade surface.

Diffstat:
MRadroots.xcodeproj/project.pbxproj | 4----
MRadroots/Runtime/TradeListing.swift | 38--------------------------------------
MRadroots/Views/TradeListingDetailView.swift | 103-------------------------------------------------------------------------------
DRadroots/Views/TradeOrderRequestView.swift | 140-------------------------------------------------------------------------------
MRadrootsFFI/Makefile | 2+-
MRadrootsFFI/source.lock | 2+-
6 files changed, 2 insertions(+), 287 deletions(-)

diff --git a/Radroots.xcodeproj/project.pbxproj b/Radroots.xcodeproj/project.pbxproj @@ -63,7 +63,6 @@ EB7C19F62D7DAB9C044D53AA /* PostDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C0EFACAD213A69C12D5064 /* PostDetailView.swift */; }; F32EFF00A8A852F76657FEE1 /* PostCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8AAF0C0F1723860A8481E0 /* PostCreateView.swift */; }; F3E40E5A76B4EA19AC7603D2 /* RadrootsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2DAD90EBF8EB00ACDD7611CD /* RadrootsKit */; }; - FD9B01F8F5DD1F05A64FD556 /* TradeOrderRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466BFA2F60BE3113EDD1BA3B /* TradeOrderRequestView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -82,7 +81,6 @@ 3B4E53FD4C4AADF63855888A /* FieldTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldTelemetry.swift; sourceTree = "<group>"; }; 3E6187FA7C4786EC662718B2 /* FieldExternalActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldExternalActions.swift; sourceTree = "<group>"; }; 41A4289F43625DD65E6C4B25 /* CopyRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyRow.swift; sourceTree = "<group>"; }; - 466BFA2F60BE3113EDD1BA3B /* TradeOrderRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeOrderRequestView.swift; sourceTree = "<group>"; }; 481E7791F4F6CF82FA3117C1 /* FieldIdentityImportFailureUITestProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldIdentityImportFailureUITestProbe.swift; sourceTree = "<group>"; }; 491D57E540BAB04F24619737 /* FieldFileAccessUITestProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldFileAccessUITestProbe.swift; sourceTree = "<group>"; }; 4BC4B7D0BB4C6D8E4B0AA4AD /* radroots.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = radroots.xcconfig; sourceTree = "<group>"; }; @@ -296,7 +294,6 @@ 16A7641E5C643B4B36CFEDA8 /* SetupView.swift */, 947ADA6D32E42ED2B40A5351 /* TradeListingCreateView.swift */, 26BAE32CD1D46033DDA1A5BB /* TradeListingDetailView.swift */, - 466BFA2F60BE3113EDD1BA3B /* TradeOrderRequestView.swift */, ); path = Views; sourceTree = "<group>"; @@ -492,7 +489,6 @@ D5C58A98C950D45AD027962A /* TradeListing.swift in Sources */, 505A5731ACDBBB0296134340 /* TradeListingCreateView.swift in Sources */, 4B44B723FF06ECC363A486BA /* TradeListingDetailView.swift in Sources */, - FD9B01F8F5DD1F05A64FD556 /* TradeOrderRequestView.swift in Sources */, 35D8223F5E169DDB4E3E87C0 /* TradeSettings.swift in Sources */, B971351ABE8E79A472B4DC7D /* View+Nav.swift in Sources */, ); diff --git a/Radroots/Runtime/TradeListing.swift b/Radroots/Runtime/TradeListing.swift @@ -10,46 +10,8 @@ public extension FieldRuntimeService { try await run { try $0.tradeListingsFetch(limit: limit, sinceUnix: sinceUnix) } } - func tradeListingSendValidationRequest( - listingEventId: String, - sellerPubkey: String, - listingId: String, - recipientPubkey: String - ) async throws -> NostrEventId { - let id = try await run { - try $0.tradeListingSendValidationRequest( - listingEventId: listingEventId, - sellerPubkey: sellerPubkey, - listingId: listingId, - recipientPubkey: recipientPubkey - ) - } - return NostrEventId(id) - } - - func tradeListingSendOrderRequest(draft: TradeOrderDraft) async throws -> TradeOrderSendResult { - try await run { try $0.tradeListingSendOrderRequest(draft: draft) } - } - - func tradeListingFetchMessages( - listingAddr: String, - limit: UInt16, - sinceUnix: UInt64? = nil - ) async throws -> [TradeListingMessageSummary] { - try await run { - try $0.tradeListingFetchMessages( - listingAddr: listingAddr, - limit: limit, - sinceUnix: sinceUnix - ) - } - } } extension TradeListingSummary: Identifiable { public var id: String { eventId } } - -extension TradeListingMessageSummary: Identifiable { - public var id: String { eventId } -} diff --git a/Radroots/Views/TradeListingDetailView.swift b/Radroots/Views/TradeListingDetailView.swift @@ -1,46 +1,10 @@ import SwiftUI -@MainActor -final class TradeListingDetailViewModel: ObservableObject { - let listing: TradeListingSummary - @Published var messages: [TradeListingMessageSummary] = [] - @Published var isLoading = false - @Published var errorMessage: String? - - init(listing: TradeListingSummary) { - self.listing = listing - } - - func refresh(app: AppState) async { - guard let service = app.runtimeService else { return } - isLoading = true - errorMessage = nil - - let listingAddr = listing.listingAddr - - do { - let items = try await service.tradeListingFetchMessages( - listingAddr: listingAddr, - limit: 80, - sinceUnix: nil - ) - messages = items - isLoading = false - } catch { - errorMessage = error.fieldRuntimeMessage - isLoading = false - } - } -} - struct TradeListingDetailView: View { - @EnvironmentObject private var app: AppState let listing: TradeListingSummary - @StateObject private var vm: TradeListingDetailViewModel init(listing: TradeListingSummary) { self.listing = listing - _vm = StateObject(wrappedValue: TradeListingDetailViewModel(listing: listing)) } var body: some View { @@ -73,48 +37,10 @@ struct TradeListingDetailView: View { CopyRow(title: "Seller", value: listing.sellerPubkey) } header: { Text("Event") - } footer: { - Text("Order and validation request flows are retired in this field runtime pass.") - .foregroundStyle(.secondary) - } - - Section("Activity") { - if let error = vm.errorMessage { - Text(error) - .foregroundStyle(.red) - .font(.footnote) - } - - if vm.messages.isEmpty { - ContentUnavailableView( - "No Activity Yet", - systemImage: "bubble.left.and.text.bubble.right", - description: Text("The current field runtime exposes listing publish and fetch first.") - ) - .listRowBackground(Color.clear) - } else { - ForEach(vm.messages) { message in - TradeListingMessageRow(message: message) - } - } } } .listStyle(.insetGrouped) .inlineNavigationTitle(listing.title) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - if vm.isLoading { ProgressView() } - } - ToolbarItem(placement: .topBarTrailing) { - Button { - Task { await vm.refresh(app: app) } - } label: { - Image(systemName: "arrow.clockwise") - } - } - } - .task { await vm.refresh(app: app) } - .refreshable { await vm.refresh(app: app) } } private var priceLine: String { @@ -131,32 +57,3 @@ struct TradeListingDetailView: View { } } - -private struct TradeListingMessageRow: View { - let message: TradeListingMessageSummary - - var body: some View { - VStack(alignment: .leading, spacing: 6) { - Text(message.summary) - .font(.subheadline.weight(.semibold)) - .foregroundStyle(.primary) - HStack { - Text(message.messageType.replacingOccurrences(of: "_", with: " ").capitalized) - .font(.caption) - .foregroundStyle(.secondary) - Spacer() - Text(relativeTime(message.publishedAt)) - .font(.caption) - .foregroundStyle(.secondary) - } - } - .padding(.vertical, 4) - } - - private func relativeTime(_ unix: UInt64) -> String { - let d = Date(timeIntervalSince1970: TimeInterval(unix)) - let f = RelativeDateTimeFormatter() - f.unitsStyle = .abbreviated - return f.localizedString(for: d, relativeTo: Date()) - } -} diff --git a/Radroots/Views/TradeOrderRequestView.swift b/Radroots/Views/TradeOrderRequestView.swift @@ -1,140 +0,0 @@ -import SwiftUI -import Foundation - -struct TradeOrderRequestView: View { - @EnvironmentObject private var app: AppState - @Environment(\.dismiss) private var dismiss - let listing: TradeListingSummary - private let onComplete: (TradeOrderSendResult) -> Void - - @State private var binCount: String - @State private var notes: String = "" - @State private var isSending = false - @State private var errorMessage: String? - @FocusState private var focused: Bool - - init(listing: TradeListingSummary, onComplete: @escaping (TradeOrderSendResult) -> Void) { - self.listing = listing - self.onComplete = onComplete - _binCount = State(initialValue: "1") - } - - var body: some View { - Form { - Section("Order") { - TextField("Bin count", text: $binCount) - .keyboardType(.numberPad) - .focused($focused) - LabeledContent("Bin size", value: binLine) - LabeledContent("Unit price", value: unitPriceLine) - if let total = totalPriceLine { - LabeledContent("Estimated total", value: total) - } - } - - Section("Notes") { - TextEditor(text: $notes) - .frame(minHeight: 100) - } - - Section { - SectionWideButton("Send Order Request", enabled: canSend, isProminent: true) { - sendOrder() - } - } footer: { - if let errorMessage { - Text(errorMessage).foregroundStyle(.red) - } else if TradeSettings.rhiPubkeyOptional == nil { - Text("Set RADROOTS_FIELD_IOS_TRADE_RHI_PUBKEY to enable order requests.") - .foregroundStyle(.secondary) - } - } - } - .scrollDismissesKeyboard(.interactively) - .inlineNavigationTitle("Order Request") - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - if isSending { ProgressView() } - } - ToolbarItemGroup(placement: .keyboard) { - Spacer() - Button("Done") { focused = false } - } - } - .onAppear { focused = true } - } - - private var unitPriceLine: String { - "\(listing.unitPriceAmount) \(listing.unitPriceCurrency) / \(listing.unitPriceUnit)" - } - - private var binLine: String { - let label = listing.binDisplayLabel?.trimmingCharacters(in: .whitespacesAndNewlines) - let base = "\(listing.binDisplayAmount) \(listing.binDisplayUnit)" - if let label, !label.isEmpty { - return "\(base) \(label)" - } - return base - } - - private var canSend: Bool { - app.relayConnectedCount > 0 && - !isSending && - TradeSettings.rhiPubkeyOptional != nil && - parsedBinCount != nil - } - - private var totalPriceLine: String? { - guard let countValue = parsedBinCount, - let unitPrice = Decimal(string: listing.unitPriceAmount), - let binAmount = Decimal(string: listing.binDisplayAmount) else { - return nil - } - let count = Decimal(Int(countValue)) - let total = count * unitPrice * binAmount - return "\(total) \(listing.unitPriceCurrency)" - } - - private func sendOrder() { - guard let service = app.runtimeService else { return } - guard let rhiPubkey = TradeSettings.rhiPubkeyOptional else { - errorMessage = "Missing RHI pubkey." - return - } - errorMessage = nil - isSending = true - - let trimmedNotes = notes.trimmingCharacters(in: .whitespacesAndNewlines) - guard let countValue = parsedBinCount else { - errorMessage = "Bin count must be a whole number." - isSending = false - return - } - let trimmedCount = String(countValue) - let draft = TradeOrderDraft( - listingAddr: listing.listingAddr, - sellerPubkey: listing.sellerPubkey, - binId: listing.primaryBinId, - binCount: trimmedCount, - notes: trimmedNotes.isEmpty ? nil : trimmedNotes, - orderId: nil, - recipientPubkey: rhiPubkey - ) - - Task { @MainActor in - do { - let out = try await service.tradeListingSendOrderRequest(draft: draft) - isSending = false - onComplete(out) - dismiss() - } catch { - isSending = false - errorMessage = error.fieldRuntimeMessage - } - } - } - - private var parsedBinCount: UInt32? { - UInt32(binCount.trimmingCharacters(in: .whitespacesAndNewlines)) - } -} diff --git a/RadrootsFFI/Makefile b/RadrootsFFI/Makefile @@ -6,7 +6,7 @@ SHELL := /bin/bash SOURCE_MODE ?= git RADROOTS_FIELD_LIB_GIT_URL ?= git@github.com:radrootslabs/field_lib.git -RADROOTS_FIELD_LIB_GIT_REV ?= 826c68898139e5119c6388fece76df6b853f6458 +RADROOTS_FIELD_LIB_GIT_REV ?= 39ab08d749561f74693408b70098443dddd881f8 RADROOTS_FIELD_FFI_CRATE_VERSION ?= 0.1.0-alpha.1 FFI_FEATURES ?= radroots_field_core/rt,radroots_field_core/nostr-client LOCAL_FFI_MANIFEST ?= diff --git a/RadrootsFFI/source.lock b/RadrootsFFI/source.lock @@ -1,5 +1,5 @@ SOURCE_MODE=git RADROOTS_FIELD_LIB_GIT_URL=git@github.com:radrootslabs/field_lib.git -RADROOTS_FIELD_LIB_GIT_REV=826c68898139e5119c6388fece76df6b853f6458 +RADROOTS_FIELD_LIB_GIT_REV=39ab08d749561f74693408b70098443dddd881f8 RADROOTS_FIELD_FFI_CRATE_VERSION=0.1.0-alpha.1 FFI_FEATURES=radroots_field_core/rt,radroots_field_core/nostr-client