commit 5b7bc99f3e8bf78247fd6e85f8284fe34527ddf2
parent 7ca86c3a19e7f50c7a2aa11743dba5ae2c692a3a
Author: triesap <tyson@radroots.org>
Date: Fri, 19 Jun 2026 18:09:08 -0700
ui: remove deferred Market listing surface
- delete app-authored Market and listing views
- remove the field app listing runtime wrapper
- regenerate the Xcode project without deferred listing sources
- keep generated shared FFI bindings intact for future runtime work
Diffstat:
5 files changed, 0 insertions(+), 549 deletions(-)
diff --git a/Radroots.xcodeproj/project.pbxproj b/Radroots.xcodeproj/project.pbxproj
@@ -18,13 +18,10 @@
2B3886FD26434A54F3726591 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D19AA8D515FC8F5D2407378 /* Localizable.strings */; };
2B6ACA26689B355CECBFFB57 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16A7641E5C643B4B36CFEDA8 /* SetupView.swift */; };
2FAE0FC43EB547F2CE7A567D /* FieldBackgroundExecutionUITestProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59AC0543EF8335D691D56BD3 /* FieldBackgroundExecutionUITestProbe.swift */; };
- 33A800AA701C354099623B24 /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C0B870E44C7B152A7FABE0 /* MarketView.swift */; };
360F23EFE80FDBDC6983FB15 /* AppEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D448C9655B708CA3FA8712B9 /* AppEntry.swift */; };
3A7FA9E5BCC7590B2EAC5349 /* RelaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBB081610305940C7849C7C /* RelaySettings.swift */; };
3B6020E24A2DAD8ADFC2F155 /* BuildConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71EFADBC7D54AF5B9314773 /* BuildConfig.swift */; };
3FC570AC038C3DC575E5A3E7 /* FieldDocumentInterchange.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F2BDC19EF9435162BFF5EC /* FieldDocumentInterchange.swift */; };
- 4B44B723FF06ECC363A486BA /* TradeListingDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BAE32CD1D46033DDA1A5BB /* TradeListingDetailView.swift */; };
- 505A5731ACDBBB0296134340 /* TradeListingCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947ADA6D32E42ED2B40A5351 /* TradeListingCreateView.swift */; };
5AECD474FB2F91855BDD79C0 /* PostFeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21554DA87EEC1E5C5F38365 /* PostFeedViewModel.swift */; };
5FB3E3E450EE7DFF30F3A005 /* FieldIdentityPolicyUITestProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E150C6C18B2A06F2F3227C6 /* FieldIdentityPolicyUITestProbe.swift */; };
657BEA5AAFF129E10177FE63 /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63189EB90A86A9929BECD9ED /* Nostr.swift */; };
@@ -51,7 +48,6 @@
D3E08BD0EB07C4E687BDAEF0 /* FieldBackgroundURLSessionEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DA5C6DE4731C46D53B757E3 /* FieldBackgroundURLSessionEvents.swift */; };
D4CFDE54747B6D6957977025 /* FieldIdentityPublicMetadataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA942715DB13FFD494FD35A0 /* FieldIdentityPublicMetadataStore.swift */; };
D57452A5B550E4832913AF02 /* FieldUITestSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901B3694434DC803B37651E8 /* FieldUITestSupport.swift */; };
- D5C58A98C950D45AD027962A /* TradeListing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D69D200DB1F5FA7AA561CD7 /* TradeListing.swift */; };
D62E9461833A0AA5E622A1E6 /* ToastModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227028B4EBDC6703999FB9DA /* ToastModifier.swift */; };
D9BF5BE7E4AB5EACBF342539 /* FieldSecureIdentityStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8246B707FA9D218414EC4038 /* FieldSecureIdentityStore.swift */; };
DCE468F668A3C346E716B04C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCF0F7B3C57D8D770F178329 /* Assets.xcassets */; };
@@ -65,7 +61,6 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
- 08C0B870E44C7B152A7FABE0 /* MarketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketView.swift; sourceTree = "<group>"; };
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>"; };
138AA7BAA021EE13E829390B /* Bundle+Build.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Build.swift"; sourceTree = "<group>"; };
@@ -73,7 +68,6 @@
16A7641E5C643B4B36CFEDA8 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
19D317BC9F759709098490DD /* RadrootsAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadrootsAppDelegate.swift; sourceTree = "<group>"; };
227028B4EBDC6703999FB9DA /* ToastModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastModifier.swift; sourceTree = "<group>"; };
- 26BAE32CD1D46033DDA1A5BB /* TradeListingDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeListingDetailView.swift; sourceTree = "<group>"; };
2818363B157125491FB84A1E /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
2F3541389124A31F5D701A45 /* FieldLocationCheckIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldLocationCheckIn.swift; sourceTree = "<group>"; };
32238D3DFB4465DEEDE6E003 /* FieldRuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldRuntimeError.swift; sourceTree = "<group>"; };
@@ -93,7 +87,6 @@
7BCA99336E305EC789152DDE /* radroots.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = radroots.local.xcconfig; sourceTree = "<group>"; };
7C294E8EF50F5E1E73F5C135 /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = "<group>"; };
7CC1D11E2E296B4B54E7E8A9 /* FieldCaptureIntake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCaptureIntake.swift; sourceTree = "<group>"; };
- 7D69D200DB1F5FA7AA561CD7 /* TradeListing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeListing.swift; sourceTree = "<group>"; };
8246B707FA9D218414EC4038 /* FieldSecureIdentityStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldSecureIdentityStore.swift; sourceTree = "<group>"; };
8DA5C6DE4731C46D53B757E3 /* FieldBackgroundURLSessionEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldBackgroundURLSessionEvents.swift; sourceTree = "<group>"; };
8E6A1827AB4419F806CD848F /* FieldTelemetryUITestProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldTelemetryUITestProbe.swift; sourceTree = "<group>"; };
@@ -101,7 +94,6 @@
901B3694434DC803B37651E8 /* FieldUITestSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldUITestSupport.swift; sourceTree = "<group>"; };
93AA285819DD1269C3EAD80A /* Radroots.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Radroots.app; sourceTree = BUILT_PRODUCTS_DIR; };
93D729E070C32490545FA837 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
- 947ADA6D32E42ED2B40A5351 /* TradeListingCreateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeListingCreateView.swift; sourceTree = "<group>"; };
9AE0EB327C10171444553378 /* PostFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostFeedView.swift; sourceTree = "<group>"; };
9EB0A2CFCBBFA9D204C6992B /* FieldLocalState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldLocalState.swift; sourceTree = "<group>"; };
A0B0C9861CD86EAD3CAD549E /* View+Nav.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Nav.swift"; sourceTree = "<group>"; };
@@ -227,7 +219,6 @@
63189EB90A86A9929BECD9ED /* Nostr.swift */,
8F0F21496E7A8490EB14AC5B /* Radroots.swift */,
6FBB081610305940C7849C7C /* RelaySettings.swift */,
- 7D69D200DB1F5FA7AA561CD7 /* TradeListing.swift */,
);
path = Runtime;
sourceTree = "<group>";
@@ -280,7 +271,6 @@
isa = PBXGroup;
children = (
0A0274A0260D1C04F40C71AF /* HomeView.swift */,
- 08C0B870E44C7B152A7FABE0 /* MarketView.swift */,
CA8AAF0C0F1723860A8481E0 /* PostCreateView.swift */,
F3C0EFACAD213A69C12D5064 /* PostDetailView.swift */,
9AE0EB327C10171444553378 /* PostFeedView.swift */,
@@ -289,8 +279,6 @@
C1D9496F9F05A4E79E73A247 /* RelaysView.swift */,
E1D12A016D1377CDFBFB0F9B /* SettingsView.swift */,
16A7641E5C643B4B36CFEDA8 /* SetupView.swift */,
- 947ADA6D32E42ED2B40A5351 /* TradeListingCreateView.swift */,
- 26BAE32CD1D46033DDA1A5BB /* TradeListingDetailView.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -465,7 +453,6 @@
25654E50F9519809A237759D /* FieldUserPresenceGate.swift in Sources */,
1E5B41A3E1F9A7D68F63B079 /* HomeView.swift in Sources */,
B8A3BBDE3A1FC0248512BF76 /* LoggingSettings.swift in Sources */,
- 33A800AA701C354099623B24 /* MarketView.swift in Sources */,
657BEA5AAFF129E10177FE63 /* Nostr.swift in Sources */,
F32EFF00A8A852F76657FEE1 /* PostCreateView.swift in Sources */,
EB7C19F62D7DAB9C044D53AA /* PostDetailView.swift in Sources */,
@@ -483,9 +470,6 @@
2B6ACA26689B355CECBFFB57 /* SetupView.swift in Sources */,
7FD8FB018DA09568303194B2 /* Strings.swift in Sources */,
D62E9461833A0AA5E622A1E6 /* ToastModifier.swift in Sources */,
- D5C58A98C950D45AD027962A /* TradeListing.swift in Sources */,
- 505A5731ACDBBB0296134340 /* TradeListingCreateView.swift in Sources */,
- 4B44B723FF06ECC363A486BA /* TradeListingDetailView.swift in Sources */,
B971351ABE8E79A472B4DC7D /* View+Nav.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Radroots/Runtime/TradeListing.swift b/Radroots/Runtime/TradeListing.swift
@@ -1,17 +0,0 @@
-import Foundation
-
-public extension FieldRuntimeService {
- func tradeListingPublish(draft: TradeListingDraft) async throws -> NostrEventId {
- let id = try await run { try $0.tradeListingPublish(draft: draft) }
- return NostrEventId(id)
- }
-
- func tradeListingsFetch(limit: UInt16, sinceUnix: UInt64? = nil) async throws -> [TradeListingSummary] {
- try await run { try $0.tradeListingsFetch(limit: limit, sinceUnix: sinceUnix) }
- }
-
-}
-
-extension TradeListingSummary: Identifiable {
- public var id: String { eventId }
-}
diff --git a/Radroots/Views/MarketView.swift b/Radroots/Views/MarketView.swift
@@ -1,181 +0,0 @@
-import SwiftUI
-
-@MainActor
-final class TradeListingsViewModel: ObservableObject {
- @Published var listings: [TradeListingSummary] = []
- @Published var isLoading = false
- @Published var errorMessage: String?
- @Published var searchText: String = ""
-
- func loadIfNeeded(app: AppState) async {
- if listings.isEmpty {
- await refresh(app: app)
- }
- }
-
- func refresh(app: AppState) async {
- guard let service = app.runtimeService else { return }
- isLoading = true
- errorMessage = nil
-
- do {
- let items = try await service.tradeListingsFetch(limit: 60, sinceUnix: nil)
- listings = items
- isLoading = false
- } catch {
- errorMessage = error.fieldRuntimeMessage
- isLoading = false
- }
- }
-}
-
-struct MarketView: View {
- @EnvironmentObject private var app: AppState
- @StateObject private var vm = TradeListingsViewModel()
- @State private var showCreate = false
-
- var body: some View {
- List {
- if let error = vm.errorMessage {
- Section {
- Text(error)
- .foregroundStyle(.red)
- .font(.footnote)
- }
- }
-
- if filteredListings.isEmpty {
- if vm.isLoading {
- HStack {
- Spacer()
- ProgressView()
- Spacer()
- }
- .listRowBackground(Color.clear)
- } else {
- ContentUnavailableView(
- "No Listings Yet",
- systemImage: "leaf",
- description: Text("Connect to relays and pull listings from the network.")
- )
- .listRowBackground(Color.clear)
- }
- } else {
- Section {
- ForEach(filteredListings) { listing in
- NavigationLink {
- TradeListingDetailView(listing: listing)
- } label: {
- TradeListingRow(listing: listing)
- }
- }
- }
- }
- }
- .listStyle(.plain)
- .navigationTitle("Market")
- .accessibilityIdentifier("field_ios.market")
- .toolbar {
- ToolbarItem(placement: .topBarTrailing) {
- if vm.isLoading {
- ProgressView()
- }
- }
- ToolbarItem(placement: .topBarTrailing) {
- Button {
- showCreate = true
- } label: {
- Image(systemName: "plus")
- }
- }
- }
- .searchable(text: $vm.searchText, placement: .navigationBarDrawer(displayMode: .always))
- .task { await vm.loadIfNeeded(app: app) }
- .refreshable { await vm.refresh(app: app) }
- .sheet(isPresented: $showCreate) {
- NavigationStack {
- TradeListingCreateView {
- Task { await vm.refresh(app: app) }
- }
- }
- }
- }
-
- private var filteredListings: [TradeListingSummary] {
- let trimmed = vm.searchText.trimmingCharacters(in: .whitespacesAndNewlines)
- guard !trimmed.isEmpty else { return vm.listings }
- let needle = trimmed.lowercased()
- return vm.listings.filter { listing in
- let haystack = [
- listing.title,
- listing.description,
- listing.productType,
- listing.location,
- listing.sellerPubkey
- ]
- .joined(separator: " ")
- .lowercased()
- return haystack.contains(needle)
- }
- }
-}
-
-private struct TradeListingRow: View {
- let listing: TradeListingSummary
-
- var body: some View {
- VStack(alignment: .leading, spacing: 6) {
- HStack(alignment: .firstTextBaseline) {
- Text(listing.title)
- .font(.headline)
- .foregroundStyle(.primary)
- Spacer()
- Text(listing.availability.capitalized)
- .font(.caption.weight(.semibold))
- .foregroundStyle(.secondary)
- }
-
- if !listing.description.isEmpty {
- Text(listing.description)
- .font(.subheadline)
- .foregroundStyle(.secondary)
- .lineLimit(2)
- }
-
- HStack {
- Text(priceLine)
- .font(.subheadline.weight(.semibold))
- .foregroundStyle(.primary)
- Spacer()
- Text("Inventory \(listing.inventoryAvailable)")
- .font(.subheadline)
- .foregroundStyle(.secondary)
- }
- Text(binLine)
- .font(.caption)
- .foregroundStyle(.secondary)
-
- HStack(spacing: 12) {
- Label(listing.deliveryMethod.capitalized, systemImage: "truck.box")
- Label(listing.location, systemImage: "mappin.and.ellipse")
- }
- .font(.caption)
- .foregroundStyle(.secondary)
- .lineLimit(1)
- }
- .padding(.vertical, 4)
- }
-
- private var priceLine: String {
- "\(listing.unitPriceAmount) \(listing.unitPriceCurrency) / \(listing.unitPriceUnit)"
- }
-
- private var binLine: String {
- let label = listing.binDisplayLabel?.trimmingCharacters(in: .whitespacesAndNewlines)
- let base = "Bin \(listing.binDisplayAmount) \(listing.binDisplayUnit)"
- if let label, !label.isEmpty {
- return "\(base) \(label)"
- }
- return base
- }
-}
diff --git a/Radroots/Views/TradeListingCreateView.swift b/Radroots/Views/TradeListingCreateView.swift
@@ -1,276 +0,0 @@
-import SwiftUI
-
-struct TradeListingCreateView: View {
- @EnvironmentObject private var app: AppState
- @Environment(\.dismiss) private var dismiss
- private var onCreated: (() -> Void)?
-
- @State private var draft = ListingDraftState()
- @State private var isPosting = false
- @State private var errorMessage: String?
- @FocusState private var focusedField: Field?
-
- init(onCreated: (() -> Void)? = nil) {
- self.onCreated = onCreated
- }
-
- var body: some View {
- Form {
- Section("Farm") {
- TextField("Farm pubkey", text: $draft.farmPubkey)
- .textInputAutocapitalization(.none)
- .autocorrectionDisabled()
- .submitLabel(.next)
- .focused($focusedField, equals: .farmPubkey)
- .onSubmit { focusedField = .farmDTag }
-
- TextField("Farm id", text: $draft.farmDTag)
- .textInputAutocapitalization(.none)
- .autocorrectionDisabled()
- .submitLabel(.next)
- .focused($focusedField, equals: .farmDTag)
- .onSubmit { focusedField = .title }
- }
-
- Section("Listing") {
- TextField("Title", text: $draft.title)
- .textInputAutocapitalization(.words)
- .submitLabel(.next)
- .focused($focusedField, equals: .title)
- .onSubmit { focusedField = .description }
-
- TextEditor(text: $draft.description)
- .frame(minHeight: 120)
- .focused($focusedField, equals: .description)
- }
-
- Section("Product") {
- TextField("Category", text: $draft.category)
- .textInputAutocapitalization(.words)
- .submitLabel(.next)
- .focused($focusedField, equals: .category)
- .onSubmit { focusedField = .unitPrice }
- }
-
- Section("Pricing") {
- TextField("Unit price", text: $draft.unitPrice)
- .keyboardType(.decimalPad)
- .focused($focusedField, equals: .unitPrice)
-
- TextField("Currency", text: $draft.currency)
- .textInputAutocapitalization(.characters)
- .autocorrectionDisabled()
- .focused($focusedField, equals: .currency)
-
- HStack {
- TextField("Bin size", text: $draft.binDisplayAmount)
- .keyboardType(.decimalPad)
- .focused($focusedField, equals: .binDisplayAmount)
- Picker("Unit", selection: $draft.binDisplayUnit) {
- ForEach(ListingDraftState.UnitOption.allCases, id: \.self) { unit in
- Text(unit.label).tag(unit)
- }
- }
- .pickerStyle(.menu)
- }
-
- TextField("Bin label (optional)", text: $draft.binLabel)
- .textInputAutocapitalization(.words)
- .focused($focusedField, equals: .binLabel)
-
- TextField("Inventory available", text: $draft.inventory)
- .keyboardType(.decimalPad)
- .focused($focusedField, equals: .inventory)
- }
-
- Section("Delivery") {
- Picker("Method", selection: $draft.deliveryMethod) {
- ForEach(ListingDraftState.DeliveryMethod.allCases, id: \.self) { method in
- Text(method.label).tag(method)
- }
- }
-
- TextField("Location", text: $draft.locationPrimary)
- .textInputAutocapitalization(.words)
- .focused($focusedField, equals: .locationPrimary)
-
- TextField("City", text: $draft.locationCity)
- .textInputAutocapitalization(.words)
- .focused($focusedField, equals: .locationCity)
-
- TextField("Region", text: $draft.locationRegion)
- .textInputAutocapitalization(.words)
- .focused($focusedField, equals: .locationRegion)
-
- TextField("Country", text: $draft.locationCountry)
- .textInputAutocapitalization(.characters)
- .focused($focusedField, equals: .locationCountry)
- }
-
- Section {
- SectionWideButton("Publish Listing", enabled: canPublish, isProminent: true) {
- publish()
- }
- } footer: {
- if let errorMessage {
- Text(errorMessage).foregroundStyle(.red)
- } else if app.relayConnectedCount == 0 {
- Text("No relays connected. Configure relays before publishing.")
- .foregroundStyle(.secondary)
- }
- }
- }
- .scrollDismissesKeyboard(.interactively)
- .inlineNavigationTitle("New Listing")
- .toolbar {
- ToolbarItem(placement: .topBarTrailing) {
- if isPosting { ProgressView() }
- }
- ToolbarItemGroup(placement: .keyboard) {
- Spacer()
- Button("Done") { focusedField = nil }
- }
- }
- }
-
- private var canPublish: Bool {
- app.relayConnectedCount > 0 && !isPosting && draft.isValid
- }
-
- private func publish() {
- guard let service = app.runtimeService else { return }
- errorMessage = nil
- isPosting = true
- let draftValue = draft.toTradeListingDraft()
-
- Task { @MainActor in
- do {
- _ = try await service.tradeListingPublish(draft: draftValue)
- isPosting = false
- onCreated?()
- dismiss()
- } catch {
- isPosting = false
- errorMessage = error.fieldRuntimeMessage
- }
- }
- }
-}
-
-private enum Field: Hashable {
- case farmPubkey
- case farmDTag
- case title
- case description
- case category
- case unitPrice
- case currency
- case binDisplayAmount
- case binLabel
- case inventory
- case locationPrimary
- case locationCity
- case locationRegion
- case locationCountry
-}
-
-private struct ListingDraftState {
- enum UnitOption: String, CaseIterable {
- case each
- case lb
- case oz
- case g
- case kg
- case l
- case ml
-
- var label: String {
- switch self {
- case .each: return "Each"
- case .lb: return "lb"
- case .oz: return "oz"
- case .g: return "g"
- case .kg: return "kg"
- case .l: return "L"
- case .ml: return "mL"
- }
- }
- }
-
- enum DeliveryMethod: String, CaseIterable {
- case pickup
- case localDelivery
- case shipping
-
- var label: String {
- switch self {
- case .pickup: return "Pickup"
- case .localDelivery: return "Local delivery"
- case .shipping: return "Shipping"
- }
- }
-
- var rawValueString: String {
- switch self {
- case .pickup: return "pickup"
- case .localDelivery: return "local_delivery"
- case .shipping: return "shipping"
- }
- }
- }
-
- var title: String = ""
- var description: String = ""
- var category: String = ""
- var farmPubkey: String = ""
- var farmDTag: String = ""
- var binDisplayUnit: UnitOption = .lb
- var binDisplayAmount: String = "1"
- var unitPrice: String = ""
- var currency: String = "USD"
- var binLabel: String = ""
- var inventory: String = ""
- var deliveryMethod: DeliveryMethod = .shipping
- var locationPrimary: String = ""
- var locationCity: String = ""
- var locationRegion: String = ""
- var locationCountry: String = ""
-
- var isValid: Bool {
- !farmPubkey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !farmDTag.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !category.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !binDisplayAmount.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !unitPrice.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !currency.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !inventory.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !locationPrimary.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
- }
-
- func toTradeListingDraft() -> TradeListingDraft {
- let trimmedLabel = binLabel.trimmingCharacters(in: .whitespacesAndNewlines)
- return TradeListingDraft(
- listingId: nil,
- farmPubkey: farmPubkey,
- farmDTag: farmDTag,
- title: title,
- description: description,
- category: category,
- binDisplayAmount: binDisplayAmount,
- binDisplayUnit: binDisplayUnit.rawValue,
- unitPrice: unitPrice,
- currency: currency,
- binLabel: trimmedLabel.isEmpty ? nil : trimmedLabel,
- binId: nil,
- inventory: inventory,
- deliveryMethod: deliveryMethod.rawValueString,
- locationPrimary: locationPrimary,
- locationCity: locationCity.isEmpty ? nil : locationCity,
- locationRegion: locationRegion.isEmpty ? nil : locationRegion,
- locationCountry: locationCountry.isEmpty ? nil : locationCountry
- )
- }
-
-}
diff --git a/Radroots/Views/TradeListingDetailView.swift b/Radroots/Views/TradeListingDetailView.swift
@@ -1,59 +0,0 @@
-import SwiftUI
-
-struct TradeListingDetailView: View {
- let listing: TradeListingSummary
-
- init(listing: TradeListingSummary) {
- self.listing = listing
- }
-
- var body: some View {
- List {
- Section("Listing") {
- LabeledContent("Title", value: listing.title)
- if !listing.description.isEmpty {
- LabeledContent("Description", value: listing.description)
- }
- LabeledContent("Category", value: listing.productType)
- if !listing.availability.isEmpty {
- LabeledContent("Availability", value: listing.availability.capitalized)
- }
- }
-
- Section("Pricing") {
- LabeledContent("Unit price", value: priceLine)
- LabeledContent("Bin size", value: binLine)
- LabeledContent("Inventory", value: listing.inventoryAvailable)
- }
-
- Section("Delivery") {
- LabeledContent("Method", value: listing.deliveryMethod.capitalized)
- LabeledContent("Location", value: listing.location)
- }
-
- Section {
- CopyRow(title: "Listing ID", value: listing.listingId)
- CopyRow(title: "Event ID", value: listing.eventId)
- CopyRow(title: "Seller", value: listing.sellerPubkey)
- } header: {
- Text("Event")
- }
- }
- .listStyle(.insetGrouped)
- .inlineNavigationTitle(listing.title)
- }
-
- private var priceLine: 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
- }
-
-}