[#102] [Functional] Full Wallet History

- initial frame connected to available data (progress)
- zboto balance
- $ balance
- inner / outer progress logic
- isDownloading and isUpToDate controls added
- outer circle parametrical solution implemented
- unit tests fixed
- SyncStatusSnapshot implemented
- snapshot tests

[102] [Functional] Full Wallet History

- latest design updates
- solved issue with the List and accessory views

[102] [Functional] Full Wallet History

- fixes for the 'all' button
- reduced actions for the detail binding

[102] [Functional] Full Wallet History

- padding fixes (all events are visible)
- home screen drawer's height set to show 3 events on screen

[102] [Functional] Full Wallet History

- final UI for all states of transaction for rows
- Lottie package added
- Lottie animation for the pending transaction added

[102] [Functional] Full Wallet History

- snapshot tests cleaned up and refactored
- snapshotting all 4 types of transaction wallet events rows (sent, pending, received, failed)

[102] [Functional] Full Wallet History (393)

- Rubik fonts

[102] [Functional] Full Wallet History (393)

- leading padding fixed
- UITableView appearance moved to onAppear()

[153] [Scaffold] Progress Status Circular Bar (#389)

- rebased
This commit is contained in:
Lukas Korba 2022-07-08 20:27:18 +02:00 committed by GitHub
parent 282fdbcdf0
commit 5b158b9e72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 509 additions and 147 deletions

View File

@ -124,6 +124,12 @@
9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; };
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; };
9E7CB6152869E8C300A02233 /* CircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6142869E8C300A02233 /* CircularProgress.swift */; };
9E6612312878337F00C75B70 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 9E6612302878337F00C75B70 /* Lottie */; };
9E6612332878338C00C75B70 /* LottieAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6612322878338C00C75B70 /* LottieAnimation.swift */; };
9E6612362878345000C75B70 /* endlessCircleProgress.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E6612352878345000C75B70 /* endlessCircleProgress.json */; };
9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; };
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; };
9E7CB6182872D3DF00A02233 /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 9E7CB6172872D3DF00A02233 /* URLRouting */; };
9E7CB61A287310EC00A02233 /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */; };
9E7CB6202874143800A02233 /* AddressDetailsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB61F2874143800A02233 /* AddressDetailsStore.swift */; };
9E7CB6212874143800A02233 /* AddressDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB61E2874143800A02233 /* AddressDetailsView.swift */; };
@ -340,6 +346,8 @@
9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextFieldStore.swift; sourceTree = "<group>"; };
9E661229287717A900C75B70 /* HomeCircularProgressSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCircularProgressSnapshotTests.swift; sourceTree = "<group>"; };
9E66122B2877188700C75B70 /* SyncStatusSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatusSnapshot.swift; sourceTree = "<group>"; };
9E6612322878338C00C75B70 /* LottieAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimation.swift; sourceTree = "<group>"; };
9E6612352878345000C75B70 /* endlessCircleProgress.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = endlessCircleProgress.json; sourceTree = "<group>"; };
9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = "<group>"; };
9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsSnapshotTests.swift; sourceTree = "<group>"; };
9E7CB6142869E8C300A02233 /* CircularProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgress.swift; sourceTree = "<group>"; };
@ -423,7 +431,8 @@
buildActionMask = 2147483647;
files = (
9EF8139827F1FAEC0075AF48 /* ZcashLightClientKit in Frameworks */,
9E7CB62C2875C6E700A02233 /* URLRouting in Frameworks */,
9E6612312878337F00C75B70 /* Lottie in Frameworks */,
9E7CB6182872D3DF00A02233 /* URLRouting in Frameworks */,
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */,
6654C73A2715A38000901167 /* ComposableArchitecture in Frameworks */,
9EAB466F285A0468002904A0 /* _URLRouting in Frameworks */,
@ -883,6 +892,14 @@
path = TransactionAddress;
sourceTree = "<group>";
};
9E6612342878341F00C75B70 /* Lotties */ = {
isa = PBXGroup;
children = (
9E6612352878345000C75B70 /* endlessCircleProgress.json */,
);
path = Lotties;
sourceTree = "<group>";
};
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */ = {
isa = PBXGroup;
children = (
@ -927,6 +944,7 @@
9E7FE0B6282D1D9800C374E8 /* Resources */ = {
isa = PBXGroup;
children = (
9E6612342878341F00C75B70 /* Lotties */,
0D4E7A0C26B364180058B01E /* Assets.xcassets */,
660558E8270C7A54009D6954 /* Colors.xcassets */,
9E37A2B727C8F59F00AE57B3 /* Localizable.strings */,
@ -955,6 +973,7 @@
9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */,
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */,
9E6612322878338C00C75B70 /* LottieAnimation.swift */,
F9C165B3274031F600592F76 /* Bindings.swift */,
F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */,
F93673D52742CB840099C6AF /* Previews.swift */,
@ -1280,7 +1299,8 @@
9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */,
9EAB466C285A0468002904A0 /* Parsing */,
9EAB466E285A0468002904A0 /* _URLRouting */,
9E7CB62B2875C6E700A02233 /* URLRouting */,
9E7CB6172872D3DF00A02233 /* URLRouting */,
9E6612302878337F00C75B70 /* Lottie */,
);
productName = secant;
productReference = 0D4E7A0526B364170058B01E /* secant-testnet.app */;
@ -1360,7 +1380,8 @@
9E2AC0FD27D8EC120042AA47 /* XCRemoteSwiftPackageReference "MnemonicSwift" */,
9EF8139627F1FAEC0075AF48 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */,
9EAB466B285A0468002904A0 /* XCRemoteSwiftPackageReference "swift-parsing" */,
9E7CB62A2875C6E700A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */,
9E7CB6162872D3DF00A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */,
9E66122F2878337F00C75B70 /* XCRemoteSwiftPackageReference "lottie-ios" */,
);
productRefGroup = 0D4E7A0626B364170058B01E /* Products */;
projectDirPath = "";
@ -1381,6 +1402,7 @@
0DACFA9327209FA70039EEA5 /* Roboto-Medium.ttf in Resources */,
0DACFA9827209FA70039EEA5 /* Roboto-MediumItalic.ttf in Resources */,
0DACFA9427209FA70039EEA5 /* Roboto-BoldItalic.ttf in Resources */,
9E6612362878345000C75B70 /* endlessCircleProgress.json in Resources */,
0DACFA9027209FA70039EEA5 /* Roboto-Bold.ttf in Resources */,
0DACFA9227209FA70039EEA5 /* Roboto-Italic.ttf in Resources */,
0D535FDE271F4214009A9E3E /* Rubik-Italic-VariableFont_wght.ttf in Resources */,
@ -1540,6 +1562,7 @@
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */,
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */,
2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */,
9E6612332878338C00C75B70 /* LottieAnimation.swift in Sources */,
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */,
9EAB4671285A1C77002904A0 /* DeeplinkHandler.swift in Sources */,
9E2AC10127D8EF0B0042AA47 /* WrappedMnemonic.swift in Sources */,
@ -1973,7 +1996,15 @@
minimumVersion = 2.0.0;
};
};
9E7CB62A2875C6E700A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */ = {
9E66122F2878337F00C75B70 /* XCRemoteSwiftPackageReference "lottie-ios" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/airbnb/lottie-ios";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.0.0;
};
};
9E7CB6162872D3DF00A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "http://github.com/pointfreeco/swift-url-routing";
requirement = {
@ -2010,9 +2041,14 @@
package = 9E2AC0FD27D8EC120042AA47 /* XCRemoteSwiftPackageReference "MnemonicSwift" */;
productName = MnemonicSwift;
};
9E7CB62B2875C6E700A02233 /* URLRouting */ = {
9E6612302878337F00C75B70 /* Lottie */ = {
isa = XCSwiftPackageProductDependency;
package = 9E7CB62A2875C6E700A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */;
package = 9E66122F2878337F00C75B70 /* XCRemoteSwiftPackageReference "lottie-ios" */;
productName = Lottie;
};
9E7CB6172872D3DF00A02233 /* URLRouting */ = {
isa = XCSwiftPackageProductDependency;
package = 9E7CB6162872D3DF00A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */;
productName = URLRouting;
};
9EAB466C285A0468002904A0 /* Parsing */ = {

View File

@ -18,6 +18,15 @@
"version" : "1.8.1"
}
},
{
"identity" : "lottie-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/airbnb/lottie-ios",
"state" : {
"revision" : "246bab7ef72bad56abefb88e84a08871cecf9cb8",
"version" : "3.4.0"
}
},
{
"identity" : "mnemonicswift",
"kind" : "remoteSourceControl",

View File

@ -18,13 +18,8 @@ struct HomeView: View {
if proxy.size.height > 0 {
Drawer(overlay: viewStore.bindingForDrawer(), maxHeight: proxy.size.height) {
VStack {
WalletEventsFlowView(store: store.historyStore())
.padding(.top, 10)
Spacer()
}
.applyScreenBackground()
WalletEventsFlowView(store: store.historyStore())
.applyScreenBackground()
}
}
}

View File

@ -11,47 +11,111 @@ struct TransactionRowView: View {
var transaction: TransactionState
var body: some View {
HStack {
Circle()
.foregroundColor(.white)
.frame(width: 30, height: 30, alignment: .center)
ZStack {
icon
VStack {
HStack {
Text(transaction.status == .received ? "Unknown paid you" : "You sent to")
.font(.system(size: 14))
.fontWeight(.bold)
HStack {
VStack {
HStack {
Text(operationTitle)
.font(.custom(FontFamily.Rubik.regular.name, size: 14))
Spacer()
Text(transaction.status == .received ? "+" : "")
.font(.custom(FontFamily.Rubik.regular.name, size: 17))
+ Text("\(transaction.zecAmount.decimalString()) ZEC")
.font(.custom(FontFamily.Rubik.regular.name, size: 17))
}
.padding(.trailing, 30)
Spacer()
Text(transaction.status == .received ? "+" : "")
+ Text("\(transaction.zecAmount.decimalString()) ZEC")
}
HStack {
Text(transaction.address)
.font(.system(size: 14))
.fontWeight(.thin)
.truncationMode(.middle)
.lineLimit(1)
Spacer(minLength: 80)
Text("$145")
.font(.system(size: 14))
.fontWeight(.thin)
HStack {
Text(transaction.address)
.foregroundColor(Asset.Colors.Text.transactionRowSubtitle.color)
.font(.custom(FontFamily.Rubik.regular.name, size: 12))
.truncationMode(.middle)
.lineLimit(1)
Spacer(minLength: 80)
// TODO: - Get the ZEC price from the SDK, issue 311, https://github.com/zcash/secant-ios-wallet/issues/311
}
.padding(.trailing, 15)
}
}
.padding(.leading, 80)
VStack {
Spacer()
Rectangle()
.padding(.horizontal, 30)
.frame(height: 1, alignment: .center)
.foregroundColor(Asset.Colors.Text.transactionRowSubtitle.color)
}
}
.frame(height: 60)
}
}
struct SendTransactionRowView_Previews: PreviewProvider {
static var previews: some View {
VStack {
TransactionRowView(transaction: .placeholder)
extension TransactionRowView {
var operationTitle: String {
switch transaction.status {
case .paid(success: _):
return "You sent to"
case .received:
return "Unknown paid you"
case .failed:
// TODO: final text to be provided, issue 392 (https://github.com/zcash/secant-ios-wallet/issues/392)
return "Transaction failed"
case .pending:
return "You are sending to"
}
.padding()
}
var icon: some View {
HStack {
switch transaction.status {
case .paid(success: _), .received:
Image(transaction.status == .received ? Asset.Assets.Icons.received.name : Asset.Assets.Icons.sent.name)
.resizable()
.frame(width: 60, height: 60)
case .failed:
// TODO: final icon to be provided, issue 392 (https://github.com/zcash/secant-ios-wallet/issues/392)
Circle()
.frame(width: 30, height: 30)
.foregroundColor(Color.red)
.padding(15)
case .pending:
LottieAnimation(
isPlaying: true,
filename: "endlessCircleProgress",
animationType: .circularLoop
)
.frame(width: 60, height: 60)
.scaleEffect(0.45)
}
Spacer()
}
.padding(.leading, 15)
}
}
struct TransactionRowView_Previews: PreviewProvider {
static var previews: some View {
TransactionRowView(
transaction:
.init(
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 10),
id: "2",
status: .paid(success: true),
timestamp: 1234567,
zecAmount: Zatoshi(amount: 123_000_000)
)
)
.preferredColorScheme(.dark)
.applyScreenBackground()
.previewLayout(.fixed(width: 428, height: 60))
}
}

View File

@ -21,6 +21,7 @@ struct WalletEventsFlowState: Equatable {
var isScrollable = false
var requiredTransactionConfirmations = 0
var walletEvents = IdentifiedArrayOf<WalletEvent>.placeholder
var selectedWalletEvent: WalletEvent?
}
// MARK: - Action
@ -81,8 +82,16 @@ extension WalletEventsFlowReducer {
state.walletEvents = IdentifiedArrayOf(uniqueElements: sortedWalletEvents)
return .none
case .updateRoute(.showWalletEvent(let walletEvent)):
state.selectedWalletEvent = walletEvent
state.route = .showWalletEvent(walletEvent)
return .none
case .updateRoute(let route):
state.route = route
if route == nil {
state.selectedWalletEvent = nil
}
return .none
case .copyToPastboard(let value):
@ -100,11 +109,21 @@ extension WalletEventsFlowReducer {
extension WalletEventsFlowViewStore {
private typealias Route = WalletEventsFlowState.Route
func bindingForSelectingWalletEvent(_ walletEvent: WalletEvent) -> Binding<Bool> {
func bindingForSelectedWalletEvent(_ walletEvent: WalletEvent?) -> Binding<Bool> {
self.binding(
get: { $0.route.map(/WalletEventsFlowState.Route.showWalletEvent) == walletEvent },
get: {
guard let walletEvent = walletEvent else {
return false
}
return $0.route.map(/WalletEventsFlowState.Route.showWalletEvent) == walletEvent
},
send: { isActive in
WalletEventsFlowAction.updateRoute( isActive ? WalletEventsFlowState.Route.showWalletEvent(walletEvent) : nil)
guard let walletEvent = walletEvent else {
return WalletEventsFlowAction.updateRoute(nil)
}
return WalletEventsFlowAction.updateRoute( isActive ? WalletEventsFlowState.Route.showWalletEvent(walletEvent) : nil)
}
)
}
@ -115,11 +134,23 @@ extension WalletEventsFlowViewStore {
extension TransactionState {
static var placeholder: Self {
.init(
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 10),
id: "2",
status: .paid(success: true),
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25)
zecAmount: Zatoshi(amount: 123_000_000)
)
}
static func statePlaceholder(_ status: Status) -> Self {
.init(
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 10),
id: "2",
status: status,
timestamp: 1234567,
zecAmount: Zatoshi(amount: 123_000_000)
)
}
}

View File

@ -3,27 +3,36 @@ import ComposableArchitecture
struct WalletEventsFlowView: View {
let store: WalletEventsFlowStore
@State var flag = true
var body: some View {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
return WithViewStore(store) { viewStore in
Group {
VStack {
header(with: viewStore)
if viewStore.isScrollable {
List {
walletEventsList(with: viewStore)
}
.listStyle(.sidebar)
.listStyle(.plain)
.padding(.bottom, 60)
} else {
walletEventsList(with: viewStore)
.padding(.horizontal, 32)
}
Spacer()
}
.onAppear(perform: { viewStore.send(.onAppear) })
.onAppear(
perform: {
UITableView.appearance().backgroundColor = .clear
UITableView.appearance().separatorColor = .clear
viewStore.send(.onAppear)
}
)
.onDisappear(perform: { viewStore.send(.onDisappear) })
.navigationLinkEmpty(isActive: viewStore.bindingForSelectedWalletEvent(viewStore.selectedWalletEvent)) {
viewStore.selectedWalletEvent?.detailView(viewStore)
}
}
}
}
@ -31,43 +40,59 @@ struct WalletEventsFlowView: View {
extension WalletEventsFlowView {
func walletEventsList(with viewStore: WalletEventsFlowViewStore) -> some View {
ForEach(viewStore.walletEvents) { walletEvent in
WithStateBinding(binding: viewStore.bindingForSelectingWalletEvent(walletEvent)) { active in
VStack {
walletEvent.rowView(viewStore)
walletEvent.rowView(viewStore)
.onTapGesture {
viewStore.send(.updateRoute(.showWalletEvent(walletEvent)))
}
.navigationLink(
isActive: active,
destination: { walletEvent.detailView(viewStore) }
)
.listRowInsets(EdgeInsets())
.foregroundColor(Asset.Colors.Text.body.color)
.listRowBackground(Color.clear)
}
}
}
func header(with viewStore: WalletEventsFlowViewStore) -> some View {
HStack(spacing: 0) {
VStack {
Button("Latest") {
Button {
viewStore.send(.updateRoute(.latest))
} label: {
Text("Latest")
.font(.custom(FontFamily.Rubik.regular.name, size: 18))
}
.frame(width: 100)
.foregroundColor(Asset.Colors.Text.drawerTabsText.color)
.opacity(viewStore.isScrollable ? 0.23 : 1.0)
Rectangle()
.frame(height: 1.5)
.foregroundColor(Asset.Colors.TextField.Underline.purple.color)
.foregroundColor(latestUnderline(viewStore))
}
VStack {
Button("All") {
Button {
viewStore.send(.updateRoute(.all))
} label: {
Text("All")
.font(.custom(FontFamily.Rubik.regular.name, size: 18))
}
.frame(width: 100)
.foregroundColor(Asset.Colors.Text.drawerTabsText.color)
.opacity(viewStore.isScrollable ? 1.0 : 0.23)
Rectangle()
.frame(height: 1.5)
.foregroundColor(Asset.Colors.TextField.Underline.gray.color)
.foregroundColor(allUnderline(viewStore))
}
}
}
private func latestUnderline(_ viewStore: WalletEventsFlowViewStore) -> Color {
viewStore.isScrollable ? Asset.Colors.TextField.Underline.gray.color : Asset.Colors.TextField.Underline.purple.color
}
private func allUnderline(_ viewStore: WalletEventsFlowViewStore) -> Color {
viewStore.isScrollable ? Asset.Colors.TextField.Underline.purple.color : Asset.Colors.TextField.Underline.gray.color
}
}
// MARK: - Previews

View File

@ -38,8 +38,12 @@ extension WalletEvent {
.failed(let transaction):
TransactionRowView(transaction: transaction)
case .shielded(let zatoshi):
// TODO: implement design once shielding is supported, issue 390
// https://github.com/zcash/secant-ios-wallet/issues/390
Text("shielded wallet event \(zatoshi.decimalString())")
case .walletImport:
// TODO: implement design once shielding is supported, issue 391
// https://github.com/zcash/secant-ios-wallet/issues/391
Text("wallet import wallet event")
}
}
@ -56,8 +60,12 @@ extension WalletEvent {
.failed(let transaction):
TransactionDetailView(transaction: transaction, viewStore: viewStore)
case .shielded(let zatoshi):
// TODO: implement design once shielding is supported, issue 390
// https://github.com/zcash/secant-ios-wallet/issues/390
Text("shielded \(zatoshi.decimalString()) detail")
case .walletImport:
// TODO: implement design once shielding is supported, issue 391
// https://github.com/zcash/secant-ios-wallet/issues/391
Text("wallet import wallet event")
}
}
@ -67,11 +75,12 @@ extension WalletEvent {
private extension WalletEvent {
static func randomWalletEventState() -> WalletEvent.WalletEventState {
switch Int.random(in: 0..<5) {
case 1: return .received(.placeholder)
case 2: return .failed(.placeholder)
switch Int.random(in: 0..<6) {
case 1: return .received(.statePlaceholder(.received))
case 2: return .failed(.statePlaceholder(.failed))
case 3: return .shielded(Zatoshi(amount: 234_000_000))
case 4: return .walletImport(BlockHeight(1_629_724))
case 5: return .pending(.statePlaceholder(.pending))
default: return .send(.placeholder)
}
}

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "received.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "received@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "received@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "sent.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "sent@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "sent@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x84",
"green" : "0x80",
"red" : "0x7D"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x84",
"green" : "0x80",
"red" : "0x7D"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -38,6 +38,8 @@ internal enum Asset {
internal static let list = ImageAsset(name: "list")
internal static let profile = ImageAsset(name: "profile")
internal static let qrCode = ImageAsset(name: "qr-code")
internal static let received = ImageAsset(name: "received")
internal static let sent = ImageAsset(name: "sent")
internal static let shield = ImageAsset(name: "shield")
internal static let swap = ImageAsset(name: "swap")
}
@ -105,6 +107,7 @@ internal enum Asset {
internal static let activeButtonText = ColorAsset(name: "ActiveButtonText")
internal static let body = ColorAsset(name: "Body")
internal static let button = ColorAsset(name: "Button")
internal static let drawerTabsText = ColorAsset(name: "DrawerTabsText")
internal static let heading = ColorAsset(name: "Heading")
internal static let importSeedEditor = ColorAsset(name: "ImportSeedEditor")
internal static let medium = ColorAsset(name: "Medium")
@ -112,6 +115,7 @@ internal enum Asset {
internal static let secondaryButtonText = ColorAsset(name: "SecondaryButtonText")
internal static let titleText = ColorAsset(name: "TitleText")
internal static let transactionDetailText = ColorAsset(name: "TransactionDetailText")
internal static let transactionRowSubtitle = ColorAsset(name: "TransactionRowSubtitle")
internal static let validMnemonic = ColorAsset(name: "ValidMnemonic")
internal static let balanceText = ColorAsset(name: "balanceText")
internal static let captionText = ColorAsset(name: "captionText")

View File

@ -0,0 +1 @@
{"v":"5.7.4","fr":60,"ip":0,"op":173,"w":512,"h":512,"nm":"Композиция 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Слой-фигура 2","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-31.206,-31.617,0],"ix":2,"l":2},"a":{"a":0,"k":[0.316,-0.493,0],"ix":1,"l":2},"s":{"a":0,"k":[94.839,94.839,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.137,0],[0,-24.137],[-24.137,0],[0,24.137]],"o":[[-24.137,0],[0,24.137],[24.137,0],[0,-24.137]],"v":[[0,-43.704],[-43.704,0],[0,43.704],[43.704,0]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[0.341176470588,0.721568627451,0.90588241278,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52,"s":[0.341176480055,0.905882418156,0.572549045086,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":102,"s":[0.336421877146,0.900118291378,0.567380785942,1]},{"t":119,"s":[0.341176480055,0.721568644047,0.905882358551,1]}],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27,"s":[4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":43,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":63,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":102,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":118,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":129,"s":[0]},{"t":142,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.316,-0.493],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Эллипс 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Слой-фигура 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2,"l":2},"a":{"a":0,"k":[-31,-32,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":26,"s":[100,100,100]},{"i":{"x":[0,0,0.667],"y":[0.965,0.965,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":48,"s":[141,141,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":63,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[-0.061,-0.061,0]},"t":102,"s":[100,100,100]},{"i":{"x":[0,0,0.667],"y":[1.084,1.084,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":123,"s":[150,150,100]},{"t":141,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[19.128,0],[0,-19.128],[-19.128,0],[0,19.128]],"o":[[-19.128,0],[0,19.128],[19.128,0],[0,-19.128]],"v":[[0,-34.635],[-34.635,0],[0,34.635],[34.635,0]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.89503401214,0.89503401214,0.89503401214,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[0.342066297344,0.721552889955,0.90709623449,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":49,"s":[0.342066287994,0.907096207142,0.574288725853,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":101,"s":[0.336265683174,0.898185789585,0.567210018635,1]},{"t":124,"s":[0.341176480055,0.721568644047,0.905882358551,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-31,-32],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Эллипс 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}

View File

@ -23,7 +23,7 @@ struct Drawer<Content: View>: View {
private var offset: CGFloat {
switch overlay {
case .full: return 70.0
case .partial: return maxHeight * 0.75
case .partial: return maxHeight - 230.0
case .bottom: return maxHeight
}
}
@ -86,7 +86,7 @@ struct Drawer_Previews: PreviewProvider {
return Drawer(overlay: $overlay, maxHeight: 800.0) {
VStack {
Text("Transaction History")
Spacer()
}
}

View File

@ -0,0 +1,80 @@
//
// LottieView.swift
// lottie-test
//
// Created by Francisco Gindre on 1/30/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import SwiftUI
import Lottie
struct LottieAnimation: UIViewRepresentable {
enum AnimationType {
case progress(progress: Float)
case frameProgress(startFrame: Float, endFrame: Float, progress: Float, loop: Bool)
case circularLoop
case playOnce
}
var isPlaying = false
var filename: String
var animationType: AnimationType
class Coordinator: NSObject {
var lastProgress: Float
var parent: LottieAnimation
init(parent: LottieAnimation) {
self.parent = parent
if case AnimationType.frameProgress(let startFrame, _, _, _) = self.parent.animationType {
self.lastProgress = startFrame
} else {
self.lastProgress = 0
}
}
}
func makeUIView(context: UIViewRepresentableContext<LottieAnimation>) -> AnimationView {
let animationView = AnimationView()
let animation = Lottie.Animation.named(filename)
animationView.backgroundBehavior = .pauseAndRestore
animationView.animation = animation
animationView.contentMode = .scaleAspectFit
return animationView
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func updateUIView(_ uiView: AnimationView, context: UIViewRepresentableContext<LottieAnimation>) {
guard isPlaying else {
uiView.stop()
return
}
switch self.animationType {
case .circularLoop:
if !uiView.isAnimationPlaying {
uiView.play(fromProgress: 0, toProgress: 1, loopMode: .loop, completion: nil)
}
case .progress(let progress):
uiView.currentProgress = AnimationProgressTime(progress)
if !uiView.isAnimationPlaying {
uiView.play(fromProgress: 0, toProgress: 1, loopMode: .loop, completion: nil)
}
case let .frameProgress(startFrame, endFrame, progress, loop):
let progressTimeFrame = AnimationFrameTime(startFrame + (progress * (endFrame - startFrame)))
uiView.play(fromFrame: nil, toFrame: progressTimeFrame, loopMode: loop ? .loop : .none, completion: nil)
context.coordinator.lastProgress = progress
case .playOnce:
uiView.play()
}
}
}

View File

@ -12,15 +12,14 @@ import ComposableArchitecture
class HomeSnapshotTests: XCTestCase {
func testHomeSnapshot() throws {
let transactionsHelper: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(amount: 1), status: .paid(success: false), uuid: "1"),
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(amount: 2), uuid: "2"),
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(amount: 3), status: .paid(success: true), uuid: "3"),
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4), uuid: "4"),
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(amount: 5), uuid: "5")
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(amount: 1), status: .paid(success: true), uuid: "1"),
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(amount: 2), status: .pending, uuid: "2"),
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(amount: 3), status: .received, uuid: "3"),
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4), status: .failed, uuid: "4")
]
let walletEvents: [WalletEvent] = transactionsHelper.map {
let transaction = TransactionState.placeholder(
var transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
@ -28,6 +27,8 @@ class HomeSnapshotTests: XCTestCase {
timestamp: $0.date,
uuid: $0.uuid
)
transaction.zAddress = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
return WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
}
@ -50,73 +51,6 @@ class HomeSnapshotTests: XCTestCase {
)
// landing home screen
addAttachments(
name: "\(#function)_initial",
HomeView(store: store)
)
// all transactions
ViewStore(store).send(.updateDrawer(.full))
addAttachments(
name: "\(#function)_fullWalletEvents",
HomeView(store: store)
)
}
func testWalletEventDetailSnapshot() throws {
let transaction = TransactionState(
memo:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true),
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
)
let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
let balance = Balance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatusSnapshot: .default,
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
environment: .demo
)
// wallet event detail
let testEnvironment = WalletEventsFlowEnvironment(
pasteboard: .test,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(),
zcashSDKEnvironment: .testnet
)
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))
let walletEventsStore = WalletEventsFlowStore(
initialState: .placeHolder,
reducer: .default,
environment: testEnvironment
)
addAttachments(
name: "\(#function)_WalletEventDetail",
TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore))
)
addAttachments(HomeView(store: store))
}
}

View File

@ -9,7 +9,59 @@ import XCTest
@testable import secant_testnet
import ComposableArchitecture
// swiftlint:disable type_body_length
class WalletEventsSnapshotTests: XCTestCase {
func testFullWalletEventsSnapshot() throws {
let transactionsHelper: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(amount: 1), status: .paid(success: true), uuid: "1"),
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(amount: 2), status: .pending, uuid: "2"),
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(amount: 3), status: .received, uuid: "3"),
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4), status: .failed, uuid: "4")
]
let walletEvents: [WalletEvent] = transactionsHelper.map {
var transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
timestamp: $0.date,
uuid: $0.uuid
)
transaction.zAddress = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
return WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
}
let balance = Balance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatusSnapshot: .default,
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents)),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
environment: .demo
)
// landing home screen
addAttachments(
name: "\(#function)_initial",
HomeView(store: store)
)
// all transactions
ViewStore(store).send(.updateDrawer(.full))
addAttachments(HomeView(store: store))
}
func testWalletEventDetailSnapshot_sent() throws {
let transaction = TransactionState(
memo: