shared-state

- implemented POC for server check in Zashi, adopted SDK with the feature

[#1237] Choose the best server by testing responses from multiple server hosts

- Logic has been updated to deliver all requirements
- UI has been upgraded to reflect latest and new design

[#1237] Choose the best server by testing responses from multiple server hosts

- inverted tint if needed implemented for the progress view

[#1237] Choose the best server by testing responses from multiple server hosts

- Rebased

[#937] Shielded transaction UI

- The TransactionState has been extended to handle shielding
- The UI for the transaction row has been extended to handle right states for all types of transactions, newly for shielding, shielded and shielding failed

[#937] Shielded transaction UI

- Rebased and updated to the latest API

[#937] Shielded transaction UI

- WIP

[#937] Shielded transaction UI

- Shileding UI updated to show amount the same way as sent/received
- Shielding UI updated to show ID

[#1337] update UI of the Settings and Advanced Settings screens

- settings WIP

[#1337] update UI of the Settings and Advanced Settings screens

- Changelog updated
- Design is finalized for both Settings and Advanced Settings

design-system

- POC done
- All Colors added/implemented
- first set of variables implemented

design-system

- Color names made unique
- CurrencyConversion screen refactored to use new design system colors
- Currency conversion colors removed from the catalogue

design-system

- Variables made for the Utilities (all of them are missing aliases in Figma so it's just prepared except a few I updated myself)
- ServerSwitch refactored to use design system
- error and primary tint updated to use design system

design-system

- Utility variables finished
- Server switch refactored to the latest design system variables

tca-deprecation-resolution

- All reducers have been updated to use @Reducer macro
- All scopes have been updated to use \. case path
- All destinations have been updated to use new Bindings

tca-deprecation-resolution

- @DependencyClient macro used for all dependencies
- Unit tests fixed

shared-state

- POC

shared-state

- Exchange rate is now handled by TCA's shared state

shared-state

- Hide balances feature has been refactored using TCA's shared state
- It's no longer treated as "hide balances" but rather "hide sensitive data" because we will add a few more things to it besides balances in the near future
- Previous approach with Combine's subject and TCA's dependency have been removed from Zashi

shared-state

- removed unused file

shared-state

- code cleanup

shared-state

- code cleanup

shared-state

- build fixed
This commit is contained in:
Lukas Korba 2024-07-08 07:48:10 -07:00
parent 126ca96ec9
commit 006b57fff7
19 changed files with 38 additions and 181 deletions

View File

@ -30,7 +30,6 @@ let package = Package(
.library(name: "FeedbackGenerator", targets: ["FeedbackGenerator"]),
.library(name: "FileManager", targets: ["FileManager"]),
.library(name: "Generated", targets: ["Generated"]),
.library(name: "HideBalances", targets: ["HideBalances"]),
.library(name: "Home", targets: ["Home"]),
.library(name: "ImportWallet", targets: ["ImportWallet"]),
.library(name: "LocalAuthenticationHandler", targets: ["LocalAuthenticationHandler"]),
@ -289,14 +288,6 @@ let package = Package(
name: "Generated",
resources: [.process("Resources")]
),
.target(
name: "HideBalances",
dependencies: [
"UserDefaults",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
],
path: "Sources/Dependencies/HideBalances"
),
.target(
name: "Home",
dependencies: [
@ -489,7 +480,6 @@ let package = Package(
"ExchangeRate",
"ExportLogs",
"Generated",
"HideBalances",
"MnemonicClient",
"Models",
"NotEnoughFreeSpace",
@ -695,7 +685,6 @@ let package = Package(
"CurrencyConversionSetup",
"ExchangeRate",
"Generated",
"HideBalances",
"Home",
"WalletStatusPanel",
"SendConfirmation",
@ -730,7 +719,6 @@ let package = Package(
"BalanceFormatter",
"DerivationTool",
"Generated",
"HideBalances",
"NumberFormatter",
"WalletStatusPanel",
"SupportDataGenerator",

View File

@ -1,28 +0,0 @@
//
// HideBalancesInterface.swift
//
//
// Created by Lukáš Korba on 11.11.2023.
//
import Foundation
import ComposableArchitecture
import Combine
extension DependencyValues {
public var hideBalances: HideBalancesClient {
get { self[HideBalancesClient.self] }
set { self[HideBalancesClient.self] = newValue }
}
}
@DependencyClient
public struct HideBalancesClient {
public enum Constants {
static let udHideBalances = "udHideBalances"
}
public var prepare: @Sendable () -> Void
public var value: @Sendable () -> CurrentValueSubject<Bool, Never> = { CurrentValueSubject(false) }
public var updateValue: @Sendable (Bool) -> Void
}

View File

@ -1,35 +0,0 @@
//
// HideBalancesLiveKey.swift
//
//
// Created by Lukáš Korba on 11.11.2023.
//
import Foundation
import ComposableArchitecture
import Combine
import UserDefaults
extension HideBalancesClient: DependencyKey {
public static var liveValue: Self {
let storage = CurrentValueSubject<Bool, Never>(false)
return .init(
prepare: {
@Dependency(\.userDefaults) var userDefaults
if let value = userDefaults.objectForKey(Constants.udHideBalances) as? Bool {
storage.value = value
}
},
value: { storage },
updateValue: {
@Dependency(\.userDefaults) var userDefaults
userDefaults.setValue($0, Constants.udHideBalances)
storage.value = $0
}
)
}
}

View File

@ -1,26 +0,0 @@
//
// HideBalancesTestKey.swift
//
//
// Created by Lukáš Korba on 11.11.2023.
//
import ComposableArchitecture
import XCTestDynamicOverlay
import Combine
extension HideBalancesClient: TestDependencyKey {
public static let testValue = Self(
prepare: unimplemented("\(Self.self).prepare", placeholder: {}()),
value: unimplemented("\(Self.self).value", placeholder: .init(false)),
updateValue: unimplemented("\(Self.self).updateValue", placeholder: {}())
)
}
extension HideBalancesClient {
public static let noOp = Self(
prepare: { },
value: { .init(false) },
updateValue: { _ in }
)
}

View File

@ -64,5 +64,5 @@ public struct SDKSynchronizerClient {
public var refreshExchangeRateUSD: () -> Void
public var evaluateBestOf: ([LightWalletEndpoint], Double, Double, UInt64, Int, NetworkType) async -> [LightWalletEndpoint]
public var evaluateBestOf: ([LightWalletEndpoint], Double, Double, UInt64, Int, NetworkType) async -> [LightWalletEndpoint] = { _,_,_,_,_,_ in [] }
}

View File

@ -23,9 +23,7 @@ public struct BalancesView: View {
@Perception.Bindable var store: StoreOf<Balances>
let tokenName: String
@Dependency(\.hideBalances) var hideBalances
@State var isHidden = false
@State private var cancellable: AnyCancellable?
@Shared(.appStorage(.sensitiveContent)) var isSensitiveContentHidden = false
@State var walletStatus = WalletStatus.none
public init(store: StoreOf<Balances>, tokenName: String) {
@ -94,18 +92,8 @@ public struct BalancesView: View {
action: \.alert
)
)
.onAppear {
store.send(.onAppear)
if !_XCTIsTesting {
cancellable = hideBalances.value().sink { val in
isHidden = val
}
}
}
.onDisappear {
store.send(.onDisappear)
cancellable?.cancel()
}
.onAppear { store.send(.onAppear) }
.onDisappear { store.send(.onDisappear) }
}
}
@ -250,7 +238,7 @@ extension BalancesView {
shadowOffset: 6
)
.padding(.bottom, 15)
.disabled(!store.isShieldableBalanceAvailable || store.isShieldingFunds || isHidden)
.disabled(!store.isShieldableBalanceAvailable || store.isShieldingFunds || isSensitiveContentHidden)
Text("(\(ZatoshiStringRepresentation.feeFormat))")
.font(.custom(FontFamily.Inter.semiBold.name, size: 11))

View File

@ -47,7 +47,6 @@ extension Root {
switch action {
case .initialization(.appDelegate(.didFinishLaunching)):
state.appStartState = .didFinishLaunching
hideBalances.prepare()
// TODO: [#704], trigger the review request logic when approved by the team,
// https://github.com/Electric-Coin-Company/zashi-ios/issues/704
return .concatenate(

View File

@ -23,7 +23,6 @@ import BackgroundTasks
import WalletStatusPanel
import Utils
import UserDefaults
import HideBalances
import ServerSetup
import ExchangeRate
@ -134,7 +133,6 @@ public struct Root {
@Dependency(\.derivationTool) var derivationTool
@Dependency(\.diskSpaceChecker) var diskSpaceChecker
@Dependency(\.exchangeRate) var exchangeRate
@Dependency(\.hideBalances) var hideBalances
@Dependency(\.mainQueue) var mainQueue
@Dependency(\.mnemonic) var mnemonic
@Dependency(\.numberFormatter) var numberFormatter

View File

@ -32,7 +32,7 @@ public struct SendFlow {
@Presents public var alert: AlertState<Action>?
public var addMemoState: Bool
public var currencyConversion: CurrencyConversion?
@Shared(.inMemory(.exchangeRate)) public var currencyConversion: CurrencyConversion? = nil
public var destination: Destination?
public var isCurrencyConversionEnabled = false
public var memoState: MessageEditor.State
@ -140,7 +140,6 @@ public struct SendFlow {
public init(
addMemoState: Bool,
currencyConversion: CurrencyConversion? = nil,
destination: Destination? = nil,
memoState: MessageEditor.State,
scanState: Scan.State,
@ -148,7 +147,6 @@ public struct SendFlow {
walletBalancesState: WalletBalances.State
) {
self.addMemoState = addMemoState
self.currencyConversion = currencyConversion
self.destination = destination
self.memoState = memoState
self.scanState = scanState

View File

@ -51,7 +51,7 @@ public struct Tabs {
}
}
}
public var addressDetailsState: AddressDetails.State
public var balanceBreakdownState: Balances.State
public var currencyConversionSetupState: CurrencyConversionSetup.State

View File

@ -16,7 +16,6 @@ import Home
import SendFlow
import Settings
import UIComponents
import HideBalances
import SendConfirmation
import CurrencyConversionSetup
@ -26,9 +25,8 @@ public struct TabsView: View {
let tokenName: String
@Namespace var tabsID
@Dependency(\.hideBalances) var hideBalances
@State var areBalancesHidden = false
@Shared(.appStorage(.sensitiveContent)) var isSensitiveContentHidden = false
public init(store: StoreOf<Tabs>, tokenName: String, networkType: NetworkType) {
self.store = store
self.tokenName = tokenName
@ -147,9 +145,6 @@ public struct TabsView: View {
.navigationBarItems(leading: hideBalancesButton(tab: store.selectedTab))
.zashiTitle { navBarView(store.selectedTab) }
.walletStatusPanel()
.onAppear {
areBalancesHidden = hideBalances.value().value
}
.overlayPreferenceValue(BoundsPreferenceKey.self) { preferences in
if store.isRateTooltipEnabled {
GeometryReader { geometry in
@ -192,11 +187,12 @@ public struct TabsView: View {
Text(L10n.CurrencyConversion.title)
.font(.custom(FontFamily.Inter.semiBold.name, size: 16))
.foregroundColor(Design.Text.primary.color)
.lineLimit(nil)
.lineLimit(1)
.minimumScaleFactor(0.5)
}
.padding(.trailing, 16)
Spacer()
Spacer(minLength: 0)
Button {
store.send(.currencyConversionCloseTapped)
@ -281,12 +277,9 @@ public struct TabsView: View {
func hideBalancesButton(tab: Tabs.State.Tab) -> some View {
if tab == .account || tab == .send || tab == .balances {
Button {
var prevValue = hideBalances.value().value
prevValue.toggle()
areBalancesHidden = prevValue
hideBalances.updateValue(areBalancesHidden)
isSensitiveContentHidden.toggle()
} label: {
let image = areBalancesHidden ? Asset.Assets.eyeOff.image : Asset.Assets.eyeOn.image
let image = isSensitiveContentHidden ? Asset.Assets.eyeOff.image : Asset.Assets.eyeOn.image
image
.renderingMode(.template)
.resizable()

View File

@ -23,7 +23,7 @@ public struct WalletBalances {
@ObservableState
public struct State: Equatable {
public var currencyConversion: CurrencyConversion?
@Shared(.inMemory(.exchangeRate)) public var currencyConversion: CurrencyConversion? = nil
public var fiatCurrencyResult: FiatCurrencyResult?
public var isAvailableBalanceTappable = true
public var isExchangeRateFeatureOn = false
@ -52,7 +52,6 @@ public struct WalletBalances {
}
public init(
currencyConversion: CurrencyConversion? = nil,
fiatCurrencyResult: FiatCurrencyResult? = nil,
isAvailableBalanceTappable: Bool = true,
isExchangeRateFeatureOn: Bool = false,
@ -64,7 +63,6 @@ public struct WalletBalances {
totalBalance: Zatoshi = .zero,
transparentBalance: Zatoshi = .zero
) {
self.currencyConversion = currencyConversion
self.fiatCurrencyResult = fiatCurrencyResult
self.isAvailableBalanceTappable = isAvailableBalanceTappable
self.isExchangeRateFeatureOn = isExchangeRateFeatureOn

View File

@ -0,0 +1,13 @@
//
// SharedStateKeys.swift
//
//
// Created by Lukáš Korba on 05-09-2024
//
import Foundation
public extension String {
static var exchangeRate = "sharedStateKey_exchangeRate"
static var sensitiveContent = "udHideBalances"
}

View File

@ -12,7 +12,6 @@ import Utils
import ComposableArchitecture
import BalanceFormatter
import XCTestDynamicOverlay
import HideBalances
import Combine
public struct ZatoshiRepresentationView: View {
@ -25,9 +24,7 @@ public struct ZatoshiRepresentationView: View {
let isFee: Bool
let couldBeHidden: Bool
@Dependency(\.hideBalances) var hideBalances
@State var isHidden = false
@State private var cancellable: AnyCancellable?
@Shared(.appStorage(.sensitiveContent)) var isSensitiveContentHidden = false
public init(
balance: Zatoshi,
@ -71,20 +68,20 @@ public struct ZatoshiRepresentationView: View {
.font(.custom(fontName, size: mostSignificantFontSize))
} else {
if format == .expanded {
Text(couldBeHidden && isHidden
Text(couldBeHidden && isSensitiveContentHidden
? L10n.General.hideBalancesMost
: zatoshiStringRepresentation.mostSignificantDigits
)
.font(.custom(fontName, size: mostSignificantFontSize))
.conditionalStrikethrough(strikethrough)
+ Text(couldBeHidden && isHidden
+ Text(couldBeHidden && isSensitiveContentHidden
? L10n.General.hideBalancesLeast
: zatoshiStringRepresentation.leastSignificantDigits
)
.font(.custom(fontName, size: leastSignificantFontSize))
.conditionalStrikethrough(strikethrough)
} else {
Text(couldBeHidden && isHidden
Text(couldBeHidden && isSensitiveContentHidden
? L10n.General.hideBalancesMostStandalone
: zatoshiStringRepresentation.mostSignificantDigits
)
@ -93,16 +90,6 @@ public struct ZatoshiRepresentationView: View {
}
}
}
.onAppear {
if !_XCTIsTesting {
cancellable = hideBalances.value().sink { val in
isHidden = val
}
}
}
.onDisappear {
cancellable?.cancel()
}
}
}

View File

@ -12,26 +12,16 @@ import Combine
import Generated
struct HiddenIfSetModifier: ViewModifier {
@Dependency(\.hideBalances) var hideBalances
@State var isHidden = false
@State private var cancellable: AnyCancellable?
@Shared(.appStorage(.sensitiveContent)) var isSensitiveContentHidden = false
func body(content: Content) -> some View {
VStack(spacing: 0) {
if isHidden {
if isSensitiveContentHidden {
Text(L10n.General.hideBalancesMostStandalone)
} else {
content
}
}
.onAppear {
cancellable = hideBalances.value().sink { val in
isHidden = val
}
}
.onDisappear {
cancellable?.cancel()
}
}
}

View File

@ -210,7 +210,7 @@
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump.git",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1",
"version" : "1.3.3"
@ -266,8 +266,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "4c4453b489cf76e6b3b0f300aba663eb78182fad",
"version" : "2.70.0"
"revision" : "9746cf80e29edfef2a39924a66731249223f42a3",
"version" : "2.72.0"
}
},
{

View File

@ -57,7 +57,6 @@ class AppInitializationTests: XCTestCase {
store.dependencies.numberFormatter = .noOp
store.dependencies.walletStatusPanel = .noOp
store.dependencies.userDefaults = .noOp
store.dependencies.hideBalances = .noOp
store.dependencies.autolockHandler = .noOp
store.dependencies.exchangeRate = .noOp
@ -144,7 +143,6 @@ class AppInitializationTests: XCTestCase {
store.dependencies.numberFormatter = .noOp
store.dependencies.walletStatusPanel = .noOp
store.dependencies.userDefaults = .noOp
store.dependencies.hideBalances = .noOp
store.dependencies.autolockHandler = .noOp
store.dependencies.exchangeRate = .noOp
@ -210,7 +208,6 @@ class AppInitializationTests: XCTestCase {
store.dependencies.walletConfigProvider = .noOp
store.dependencies.crashReporter = .noOp
store.dependencies.walletStatusPanel = .noOp
store.dependencies.hideBalances = .noOp
// Root of the test, the app finished the launch process and triggers the checks and initializations.
await store.send(.initialization(.appDelegate(.didFinishLaunching))) { state in
@ -250,7 +247,6 @@ class AppInitializationTests: XCTestCase {
store.dependencies.walletConfigProvider = .noOp
store.dependencies.crashReporter = .noOp
store.dependencies.walletStatusPanel = .noOp
store.dependencies.hideBalances = .noOp
// Root of the test, the app finished the launch process and triggers the checks and initializations.
await store.send(.initialization(.appDelegate(.didFinishLaunching))) { state in

View File

@ -31,7 +31,6 @@ class BalanceBreakdownSnapshotTests: XCTestCase {
.dependency(\.mainQueue, .immediate)
.dependency(\.walletStatusPanel, .noOp)
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
.dependency(\.hideBalances, .noOp)
.dependency(\.exchangeRate, .noOp)
}

View File

@ -50,7 +50,6 @@ class HomeSnapshotTests: XCTestCase {
.dependency(\.mainQueue, .immediate)
.dependency(\.reviewRequest, .noOp)
.dependency(\.walletStatusPanel, .noOp)
.dependency(\.hideBalances, .noOp)
.dependency(\.exchangeRate, .noOp)
}