Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Features/Contacts/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ let package = Package(
.package(name: "FeatureServices", path: "../../Packages/FeatureServices"),
.package(name: "QRScanner", path: "../../Packages/QRScanner"),
.package(name: "WalletCore", path: "../../Packages/WalletCore"),
.package(name: "ChainServices", path: "../../Packages/ChainServices"),
],
targets: [
.target(
Expand All @@ -43,6 +44,7 @@ let package = Package(
"QRScanner",
.product(name: "ContactService", package: "FeatureServices"),
.product(name: "WalletCorePrimitives", package: "WalletCore"),
.product(name: "NameService", package: "ChainServices"),
],
path: "Sources"
),
Expand All @@ -55,6 +57,7 @@ let package = Package(
.product(name: "PrimitivesTestKit", package: "Primitives"),
.product(name: "StoreTestKit", package: "Store"),
.product(name: "ContactService", package: "FeatureServices"),
.product(name: "NameServiceTestKit", package: "ChainServices"),
]
),
]
Expand Down
54 changes: 54 additions & 0 deletions Features/Contacts/Sources/Scenes/ContactsNavigationView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c). Gem Wallet. All rights reserved.

import SwiftUI
import Primitives
import Components
import ContactService
import Style

public struct ContactsNavigationView: View {

@State private var model: ContactsViewModel
@Binding private var navigationPath: NavigationPath

public init(
model: ContactsViewModel,
navigationPath: Binding<NavigationPath>
) {
_model = State(initialValue: model)
_navigationPath = navigationPath
}

public var body: some View {
ContactsScene(model: model)
.bindQuery(model.query)
.navigationTitle(model.title)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("", systemImage: SystemImage.plus, action: {
model.isPresentingAddContact = true
})
}
}
.sheet(isPresented: $model.isPresentingAddContact) {
NavigationStack {
manageContact(for: .add)
.toolbarDismissItem(type: .close, placement: .cancellationAction)
}
}
.navigationDestination(for: Scenes.Contact.self) {
manageContact(for: .edit($0.contact))
}
}

@ViewBuilder
func manageContact(for mode: ManageContactViewModel.Mode) -> some View {
ManageContactScene(
model: ManageContactViewModel(
service: model.service,
nameService: model.nameService,
mode: mode
)
)
}
}
42 changes: 5 additions & 37 deletions Features/Contacts/Sources/Scenes/ContactsScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@ import Components
import Primitives
import PrimitivesComponents
import Style
import ContactService
import Store

public struct ContactsScene: View {

@State private var model: ContactsViewModel
let model: ContactsViewModel

public init(model: ContactsViewModel) {
_model = State(initialValue: model)
self.model = model
}

public var body: some View {
List {
ForEach(model.contacts) { contact in
NavigationCustomLink(
with: ListItemView(model: model.listItemModel(for: contact)),
action: { model.isPresentingContact = contact }
)
NavigationLink(value: Scenes.Contact(contact: contact)) {
ListItemView(model: model.listItemModel(for: contact))
}
}
.onDelete(perform: model.deleteContacts)
}
Expand All @@ -36,34 +33,5 @@ public struct ContactsScene: View {
EmptyContentView(model: model.emptyContent)
}
}
.navigationTitle(model.title)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
model.isPresentingAddContact = true
} label: {
Images.System.plus
}
}
}
.sheet(isPresented: $model.isPresentingAddContact) {
ManageContactNavigationStack(
model: ManageContactViewModel(
service: model.service,
mode: .add,
onComplete: model.onAddContactComplete
)
)
}
.sheet(item: $model.isPresentingContact) { contact in
ManageContactNavigationStack(
model: ManageContactViewModel(
service: model.service,
mode: .edit(contact),
onComplete: model.onManageContactComplete
)
)
}
.bindQuery(model.query)
}
}
39 changes: 10 additions & 29 deletions Features/Contacts/Sources/Scenes/ManageContactAddressScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ public struct ManageContactAddressScene: View {
memoSection
}
}
.safeAreaButton {
StateButton(
text: model.buttonTitle,
type: .primary(model.buttonState),
action: onComplete
)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("", systemImage: SystemImage.checkmark, action: onComplete)
.disabled(model.buttonState == .disabled)
}
}
.listStyle(.insetGrouped)
.listSectionSpacing(.compact)
.navigationTitle(model.title)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
focusedField = .address
}
.sheet(isPresented: $model.isPresentingScanner) {
ScanQRCodeNavigationStack(action: onScan)
}
Expand All @@ -60,23 +62,11 @@ extension ManageContactAddressScene {

private var addressSection: some View {
Section {
InputValidationField(
AddressInputView(
model: $model.addressInputModel,
placeholder: model.addressTitle,
allowClean: true,
trailingView: {
if model.shouldShowInputActions {
HStack(spacing: .medium) {
ListButton(image: model.pasteImage, action: onSelectPaste)
ListButton(image: model.qrImage, action: onSelectScan)
}
}
}
onSelectScan: model.onSelectScan
)
.focused($focusedField, equals: .address)
.keyboardType(.alphabet)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
}

Expand All @@ -97,15 +87,6 @@ extension ManageContactAddressScene {
// MARK: - Actions

extension ManageContactAddressScene {
private func onSelectPaste() {
model.onSelectPaste()
focusedField = nil
}

private func onSelectScan() {
model.onSelectScan()
}

private func onScan(_ result: String) {
model.onHandleScan(result)
focusedField = nil
Expand Down

This file was deleted.

34 changes: 17 additions & 17 deletions Features/Contacts/Sources/Scenes/ManageContactScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,48 @@ public struct ManageContactScene: View {

@Environment(\.dismiss) private var dismiss

@Binding private var model: ManageContactViewModel
@State private var model: ManageContactViewModel

@FocusState private var focusedField: Field?
enum Field: Int, Hashable {
case name
case description
}

public init(model: Binding<ManageContactViewModel>) {
_model = model
public init(model: ManageContactViewModel) {
_model = State(initialValue: model)
}

public var body: some View {
List {
contactSection
addressesSection
}
.safeAreaButton {
StateButton(
text: model.buttonTitle,
type: .primary(model.buttonState),
action: onSave
)
}
.listStyle(.insetGrouped)
.listSectionSpacing(.compact)
.navigationTitle(model.title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: onAddAddress) {
Images.System.plus
}
Button("", systemImage: SystemImage.checkmark, action: onSave)
.disabled(model.buttonState == .disabled)
}
}
.onAppear {
if model.isAddMode {
focusedField = .name
}
}
.sheet(item: $model.isPresentingAddress) {
ManageContactAddressNavigationStack(
model: ManageContactAddressViewModel(
contactId: model.contactId,
nameService: model.nameService,
mode: $0,
onComplete: model.onAddressComplete
)
)
}
}
}

Expand All @@ -73,8 +75,6 @@ extension ManageContactScene {
allowClean: true
)
.focused($focusedField, equals: .description)
} header: {
Text(model.contactSectionTitle)
}
}

Expand All @@ -83,7 +83,7 @@ extension ManageContactScene {
ForEach(model.addresses, id: \.id) { address in
NavigationCustomLink(
with: ListItemView(model: model.listItemModel(for: address)),
action: { model.isPresentingContactAddress = address }
action: { model.isPresentingAddress = .edit(address) }
)
}
.onDelete(perform: model.deleteAddress)
Expand All @@ -105,7 +105,7 @@ extension ManageContactScene {
extension ManageContactScene {
private func onAddAddress() {
focusedField = .none
model.isPresentingAddAddress = true
model.isPresentingAddress = .add
}

private func onSave() {
Expand Down
20 changes: 10 additions & 10 deletions Features/Contacts/Sources/ViewModels/ContactsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ import PrimitivesComponents
import Components
import Store
import Localization
import Style

@Observable
@MainActor
public final class ContactsViewModel {
let service: ContactService
let nameService: any NameServiceable

public let query: ObservableQuery<ContactsRequest>
var contacts: [ContactData] { query.value }

var isPresentingContact: ContactData?
var isPresentingAddContact = false

public init(service: ContactService) {
public init(
service: ContactService,
nameService: any NameServiceable
) {
self.service = service
self.nameService = nameService
self.query = ObservableQuery(ContactsRequest(), initialValue: [])
}

Expand All @@ -33,19 +38,14 @@ public final class ContactsViewModel {
func listItemModel(for contact: ContactData) -> ListItemModel {
ListItemModel(
title: contact.contact.name,
titleStyle: TextStyle(font: .body, color: .primary, fontWeight: .semibold),
titleExtra: contact.contact.description,
titleStyleExtra: .calloutSecondary,
titleExtraLineLimit: 1,
imageStyle: .asset(assetImage: AssetImage(type: String(contact.contact.name.prefix(2))))
)
}

func onAddContactComplete() {
isPresentingAddContact = false
}

func onManageContactComplete() {
isPresentingContact = nil
}

func deleteContacts(at offsets: IndexSet) {
do {
for index in offsets {
Expand Down
Loading