Notes UI prepared and search memos
- searching in memos done (SDK changes adopted) - notes UI and flow implemented and prepared, storage is needed to enjoy full experience
This commit is contained in:
parent
67576e5e53
commit
c4e7ef9569
|
@ -73,4 +73,7 @@ public struct SDKSynchronizerClient {
|
|||
public var addProofsToPCZT: (Pczt) async throws -> Pczt
|
||||
public var createTransactionFromPCZT: (Pczt, Pczt) async throws -> CreateProposedTransactionsResult
|
||||
public var urEncoderForPCZT: (Pczt) -> UREncoder?
|
||||
|
||||
// Search
|
||||
public var fetchTxidsWithMemoContaining: (String) async throws -> [Data]
|
||||
}
|
||||
|
|
|
@ -301,6 +301,9 @@ extension SDKSynchronizerClient: DependencyKey {
|
|||
let encoder = try? keystoneSDK.generateZcashPczt(pczt_hex: pczt)
|
||||
|
||||
return encoder
|
||||
},
|
||||
fetchTxidsWithMemoContaining: { searchTerm in
|
||||
try await synchronizer.fetchTxidsWithMemoContaining(searchTerm: searchTerm)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ extension SDKSynchronizerClient: TestDependencyKey {
|
|||
createPCZTFromProposal: unimplemented("\(Self.self).createPCZTFromProposal", placeholder: Pczt()),
|
||||
addProofsToPCZT: unimplemented("\(Self.self).addProofsToPCZT", placeholder: Pczt()),
|
||||
createTransactionFromPCZT: unimplemented("\(Self.self).createTransactionFromPCZT", placeholder: .success(txIds: [])),
|
||||
urEncoderForPCZT: unimplemented("\(Self.self).urEncoderForPCZT", placeholder: nil)
|
||||
urEncoderForPCZT: unimplemented("\(Self.self).urEncoderForPCZT", placeholder: nil),
|
||||
fetchTxidsWithMemoContaining: unimplemented("\(Self.self).fetchTxidsWithMemoContaining", placeholder: [])
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -79,7 +80,8 @@ extension SDKSynchronizerClient {
|
|||
createPCZTFromProposal: { _, _ in Pczt() },
|
||||
addProofsToPCZT: { _ in Pczt() },
|
||||
createTransactionFromPCZT: { _, _ in .success(txIds: []) },
|
||||
urEncoderForPCZT: { _ in nil }
|
||||
urEncoderForPCZT: { _ in nil },
|
||||
fetchTxidsWithMemoContaining: { _ in [] }
|
||||
)
|
||||
|
||||
public static let mock = Self.mocked()
|
||||
|
@ -185,7 +187,8 @@ extension SDKSynchronizerClient {
|
|||
createPCZTFromProposal: @escaping (AccountUUID, Proposal) async throws -> Pczt = { _, _ in Pczt() },
|
||||
addProofsToPCZT: @escaping (Data) async throws -> Pczt = { _ in Pczt() },
|
||||
createTransactionFromPCZT: @escaping (Pczt, Pczt) async throws -> CreateProposedTransactionsResult = { _, _ in .success(txIds: []) },
|
||||
urEncoderForPCZT: @escaping (Pczt) -> UREncoder? = { _ in nil}
|
||||
urEncoderForPCZT: @escaping (Pczt) -> UREncoder? = { _ in nil },
|
||||
fetchTxidsWithMemoContaining: @escaping (String) async throws -> [Data] = { _ in [] }
|
||||
) -> SDKSynchronizerClient {
|
||||
SDKSynchronizerClient(
|
||||
stateStream: stateStream,
|
||||
|
@ -217,7 +220,8 @@ extension SDKSynchronizerClient {
|
|||
createPCZTFromProposal: createPCZTFromProposal,
|
||||
addProofsToPCZT: addProofsToPCZT,
|
||||
createTransactionFromPCZT: createTransactionFromPCZT,
|
||||
urEncoderForPCZT: urEncoderForPCZT
|
||||
urEncoderForPCZT: urEncoderForPCZT,
|
||||
fetchTxidsWithMemoContaining: fetchTxidsWithMemoContaining
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -728,7 +728,7 @@ public struct Tabs {
|
|||
state.transactionDetailsState.userMetadataRequest = true
|
||||
return .none
|
||||
|
||||
case .transactionDetails(.addNoteTapped(let txId)):
|
||||
case .transactionDetails(.addNoteTapped(let txId)), .transactionDetails(.saveNoteTapped(let txId)):
|
||||
state.transactionDetailsState.userMetadataRequest = false
|
||||
if let index = state.homeState.transactionListState.transactionList.index(id: txId) {
|
||||
state.homeState.transactionListState.transactionList[index].userMetadata = state.transactionDetailsState.userMetadata
|
||||
|
@ -740,6 +740,18 @@ public struct Tabs {
|
|||
}
|
||||
return .none
|
||||
|
||||
case .transactionDetails(.deleteNoteTapped(let txId)):
|
||||
state.transactionDetailsState.userMetadataRequest = false
|
||||
if let index = state.homeState.transactionListState.transactionList.index(id: txId) {
|
||||
state.homeState.transactionListState.transactionList[index].userMetadata = ""
|
||||
}
|
||||
if let index = state.transactionsManagerState.transactionList.index(id: txId) {
|
||||
state.transactionsManagerState.transactionList[index].userMetadata = ""
|
||||
state.transactionDetailsState.transaction.userMetadata = ""
|
||||
state.transactionDetailsState.userMetadata = ""
|
||||
}
|
||||
return .none
|
||||
|
||||
case .transactionDetails(.bookmarkTapped(let txId)):
|
||||
if let index = state.transactionsManagerState.transactionList.index(id: txId) {
|
||||
state.transactionsManagerState.transactionList[index].bookmarked.toggle()
|
||||
|
|
|
@ -41,6 +41,7 @@ public struct TransactionDetails {
|
|||
@Shared(.appStorage(.sensitiveContent)) var isSensitiveContentHidden = false
|
||||
public var messageStates: [MessageState] = []
|
||||
public var userMetadata = ""
|
||||
public var userMetadataOrigin = ""
|
||||
@Shared(.inMemory(.toast)) public var toast: Toast.Edge? = nil
|
||||
public var transaction: TransactionState
|
||||
public var userMetadataRequest = false
|
||||
|
@ -70,6 +71,7 @@ public struct TransactionDetails {
|
|||
case addressTapped
|
||||
case binding(BindingAction<TransactionDetails.State>)
|
||||
case bookmarkTapped(String)
|
||||
case deleteNoteTapped(String)
|
||||
case fetchedABContacts(AddressBookContacts)
|
||||
case memosLoaded([Memo])
|
||||
case messageTapped(Int)
|
||||
|
@ -77,6 +79,7 @@ public struct TransactionDetails {
|
|||
case onAppear
|
||||
case resolveMemos
|
||||
case saveAddressTapped
|
||||
case saveNoteTapped(String)
|
||||
case sendAgainTapped
|
||||
case sentToRowTapped
|
||||
case transactionIdTapped
|
||||
|
@ -124,7 +127,17 @@ public struct TransactionDetails {
|
|||
|
||||
case .binding:
|
||||
return .none
|
||||
|
||||
|
||||
case .deleteNoteTapped:
|
||||
state.userMetadata = ""
|
||||
state.transaction.userMetadata = ""
|
||||
return .none
|
||||
|
||||
case .saveNoteTapped:
|
||||
state.transaction.userMetadata = state.userMetadata
|
||||
state.userMetadataOrigin = ""
|
||||
return .none
|
||||
|
||||
case .addNoteTapped:
|
||||
state.transaction.userMetadata = state.userMetadata
|
||||
return .none
|
||||
|
@ -160,6 +173,7 @@ public struct TransactionDetails {
|
|||
return .none
|
||||
|
||||
case .noteButtonTapped:
|
||||
state.userMetadataOrigin = state.userMetadata
|
||||
return .none
|
||||
|
||||
case .bookmarkTapped:
|
||||
|
|
|
@ -116,10 +116,9 @@ public struct TransactionDetailsView: View {
|
|||
type: .tertiary
|
||||
) {
|
||||
store.send(.noteButtonTapped)
|
||||
isUserMetadataFocused = true
|
||||
}
|
||||
|
||||
if store.transaction.isSentTransaction {
|
||||
if store.transaction.isSentTransaction && !store.transaction.isShieldingTransaction {
|
||||
if store.alias == nil {
|
||||
ZashiButton(
|
||||
"Save address"
|
||||
|
@ -150,7 +149,7 @@ public struct TransactionDetailsView: View {
|
|||
store.send(.onAppear)
|
||||
}
|
||||
.sheet(isPresented: $store.userMetadataRequest) {
|
||||
userMetadataContent()
|
||||
userMetadataContent(!store.transaction.userMetadata.isEmpty)
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
|
|
@ -11,33 +11,36 @@ import Generated
|
|||
import UIComponents
|
||||
|
||||
extension TransactionDetailsView {
|
||||
@ViewBuilder func userMetadataContent() -> some View {
|
||||
@ViewBuilder func userMetadataContent(_ isEditMode: Bool) -> some View {
|
||||
WithPerceptionTracking {
|
||||
if #available(iOS 16.0, *) {
|
||||
mainBodyUM()
|
||||
mainBodyUM(isEditMode: isEditMode)
|
||||
.presentationDetents([.height(filtersSheetHeight)])
|
||||
.presentationDragIndicator(.visible)
|
||||
} else {
|
||||
mainBodyUM(stickToBottom: true)
|
||||
mainBodyUM(isEditMode: isEditMode, stickToBottom: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func mainBodyUM(stickToBottom: Bool = false) -> some View {
|
||||
@ViewBuilder func mainBodyUM(isEditMode: Bool, stickToBottom: Bool = false) -> some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if stickToBottom {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("Add a note")
|
||||
.zFont(.semiBold, size: 20, style: Design.Text.primary)
|
||||
.padding(.top, 32)
|
||||
.padding(.bottom, 16)
|
||||
Text(isEditMode
|
||||
? "Edit a note"
|
||||
: "Add a note"
|
||||
)
|
||||
.zFont(.semiBold, size: 20, style: Design.Text.primary)
|
||||
.padding(.top, 32)
|
||||
.padding(.bottom, 16)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
TextEditor(text: $store.userMetadata)
|
||||
.focused($isUserMetadataFocused)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 16))
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 16))
|
||||
.frame(height: 122)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 2)
|
||||
|
@ -54,7 +57,7 @@ extension TransactionDetailsView {
|
|||
.onTapGesture {
|
||||
isUserMetadataFocused = true
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
|
@ -66,19 +69,40 @@ extension TransactionDetailsView {
|
|||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Text("\(store.userMetadata.count)/\(TransactionDetails.State.Constants.userMetadataMaxLength) characters")
|
||||
.zFont(size: 14, style: Design.Inputs.Default.hint)
|
||||
}
|
||||
.padding(.bottom, 32)
|
||||
|
||||
ZashiButton(
|
||||
"Add note",
|
||||
type: .secondary
|
||||
) {
|
||||
store.send(.addNoteTapped(store.transaction.id))
|
||||
if isEditMode {
|
||||
HStack(spacing: 8) {
|
||||
ZashiButton(
|
||||
"Delete note",
|
||||
type: .destructive1
|
||||
) {
|
||||
store.send(.deleteNoteTapped(store.transaction.id))
|
||||
}
|
||||
|
||||
ZashiButton(
|
||||
"Save note",
|
||||
type: .secondary
|
||||
) {
|
||||
store.send(.saveNoteTapped(store.transaction.id))
|
||||
}
|
||||
.disabled(store.userMetadata == store.userMetadataOrigin)
|
||||
}
|
||||
.padding(.bottom, 24)
|
||||
} else {
|
||||
ZashiButton(
|
||||
"Add note",
|
||||
type: .secondary
|
||||
) {
|
||||
store.send(.addNoteTapped(store.transaction.id))
|
||||
}
|
||||
.disabled(store.userMetadata.isEmpty)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
.disabled(store.userMetadata.isEmpty)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
.screenHorizontalPadding()
|
||||
.background {
|
||||
|
|
|
@ -74,6 +74,7 @@ public struct TransactionsManager {
|
|||
}
|
||||
|
||||
public enum Action: BindableAction, Equatable {
|
||||
case asynchronousMemoSearchResult([String])
|
||||
case applyFiltersTapped
|
||||
case binding(BindingAction<TransactionsManager.State>)
|
||||
case eraseSearchTermTapped
|
||||
|
@ -270,17 +271,36 @@ public struct TransactionsManager {
|
|||
if !state.searchTerm.isEmpty && state.searchTerm.count >= 2 {
|
||||
state.searchedTransactionsList.removeAll()
|
||||
|
||||
// synchronous search
|
||||
state.transactionList.forEach { transaction in
|
||||
if checkSearchTerm(state.searchTerm, transaction: transaction, addressBookContacts: state.addressBookContacts) {
|
||||
state.searchedTransactionsList.append(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
// asynchronous search
|
||||
return .run { [searchTerm = state.searchTerm] send in
|
||||
let txids = try? await sdkSynchronizer.fetchTxidsWithMemoContaining(searchTerm).map {
|
||||
$0.toHexStringTxId()
|
||||
}
|
||||
|
||||
if let txids {
|
||||
await send(.asynchronousMemoSearchResult(txids))
|
||||
} else {
|
||||
await send(.updateTransactionsAccordingToFilters)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.searchedTransactionsList = state.transactionList
|
||||
}
|
||||
|
||||
return .send(.updateTransactionsAccordingToFilters)
|
||||
|
||||
case .asynchronousMemoSearchResult(let txids):
|
||||
let results = state.transactionList.filter { txids.contains($0.id) }
|
||||
state.searchedTransactionsList.append(contentsOf: results)
|
||||
return .send(.updateTransactionsAccordingToFilters)
|
||||
|
||||
case .updateTransactionsAccordingToFilters:
|
||||
// modify the initial list of all transactions according to active filters
|
||||
if !state.activeFilters.isEmpty {
|
||||
|
@ -423,11 +443,11 @@ extension TransactionsManager {
|
|||
}
|
||||
}
|
||||
|
||||
// fulsearch amounts
|
||||
// fullsearch amounts
|
||||
if input.contains(searchTerm) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -2514,7 +2514,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
|
@ -2545,7 +2545,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
@ -2575,7 +2575,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
|
Loading…
Reference in New Issue