Merge pull request #123 from zcash/feature/recovery-phrase-display

[Scaffold] Recovery Phrase Display
This commit is contained in:
Francisco Gindre 2021-12-13 16:05:36 -03:00 committed by GitHub
commit afdd071341
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 704 additions and 84 deletions

View File

@ -14,6 +14,7 @@
0D1922ED26BDE0C600052649 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1922EC26BDE0C600052649 /* AppRouter.swift */; };
0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */; };
0D1922F826BDEB3500052649 /* MockServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1922F726BDEB3500052649 /* MockServices.swift */; };
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; };
0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */; };
0D32281926C5864B00262533 /* ProfileScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D32281726C5864B00262533 /* ProfileScreen.swift */; };
0D32281A26C5864B00262533 /* ProfileScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D32281826C5864B00262533 /* ProfileScreenViewModel.swift */; };
@ -30,6 +31,8 @@
0D354A0926D5A9D000315F45 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0626D5A9D000315F45 /* Services.swift */; };
0D354A0A26D5A9D000315F45 /* KeyStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0726D5A9D000315F45 /* KeyStoring.swift */; };
0D354A0B26D5A9D000315F45 /* MnemonicSeedPhraseHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0826D5A9D000315F45 /* MnemonicSeedPhraseHandling.swift */; };
0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */; };
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */; };
0D4E7A0926B364170058B01E /* SecantApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0826B364170058B01E /* SecantApp.swift */; };
0D4E7A0B26B364170058B01E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0A26B364170058B01E /* ContentView.swift */; };
0D4E7A0D26B364180058B01E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0C26B364180058B01E /* Assets.xcassets */; };
@ -46,6 +49,8 @@
0D864A0A26E154FD00A61879 /* InitFailedScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0826E154FD00A61879 /* InitFailedScreenViewModel.swift */; };
0D864A0E26E1583000A61879 /* LoadingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0C26E1583000A61879 /* LoadingScreen.swift */; };
0D864A0F26E1583000A61879 /* LoadingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0D26E1583000A61879 /* LoadingScreenViewModel.swift */; };
0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8A43C3272AEEDE005A6414 /* SecantTextStyles.swift */; };
0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8A43C5272B129C005A6414 /* WordChipGrid.swift */; };
0DA13C8F26C15D1D00E3B610 /* WelcomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA13C8D26C15D1D00E3B610 /* WelcomeScreen.swift */; };
0DA13C9026C15D1D00E3B610 /* WelcomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA13C8E26C15D1D00E3B610 /* WelcomeScreenViewModel.swift */; };
0DA13C9726C186FF00E3B610 /* RestoreWalletScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA13C9526C186FF00E3B610 /* RestoreWalletScreen.swift */; };
@ -72,6 +77,7 @@
0DB8AA81271DC7520035BC9D /* DesignGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB8AA80271DC7520035BC9D /* DesignGuide.swift */; };
0DF2DC51272344E400FA31E2 /* EmptyChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF2DC50272344E400FA31E2 /* EmptyChip.swift */; };
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF2DC5327235E3E00FA31E2 /* View+InnerShadow.swift */; };
0DFE93DF272C6D4B000FCCA5 /* RecoveryFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFE93DE272C6D4B000FCCA5 /* RecoveryFlowTests.swift */; };
2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */; };
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */; };
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5C27467F7700709571 /* OnboardingContentView.swift */; };
@ -135,6 +141,7 @@
0D1922EC26BDE0C600052649 /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = "<group>"; };
0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashSDKStubs.swift; sourceTree = "<group>"; };
0D1922F726BDEB3500052649 /* MockServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServices.swift; sourceTree = "<group>"; };
0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayReducerTests.swift; sourceTree = "<group>"; };
0D2ACE7F26C2C67100D62E3C /* Zboto.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Zboto.otf; sourceTree = "<group>"; };
0D32281726C5864B00262533 /* ProfileScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileScreen.swift; sourceTree = "<group>"; };
0D32281826C5864B00262533 /* ProfileScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileScreenViewModel.swift; sourceTree = "<group>"; };
@ -151,6 +158,8 @@
0D354A0626D5A9D000315F45 /* Services.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
0D354A0726D5A9D000315F45 /* KeyStoring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyStoring.swift; sourceTree = "<group>"; };
0D354A0826D5A9D000315F45 /* MnemonicSeedPhraseHandling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MnemonicSeedPhraseHandling.swift; sourceTree = "<group>"; };
0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayView.swift; sourceTree = "<group>"; };
0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayStore.swift; sourceTree = "<group>"; };
0D4E7A0526B364170058B01E /* secant-testnet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "secant-testnet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
0D4E7A0826B364170058B01E /* SecantApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantApp.swift; sourceTree = "<group>"; };
0D4E7A0A26B364170058B01E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@ -173,6 +182,8 @@
0D864A0826E154FD00A61879 /* InitFailedScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitFailedScreenViewModel.swift; sourceTree = "<group>"; };
0D864A0C26E1583000A61879 /* LoadingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingScreen.swift; sourceTree = "<group>"; };
0D864A0D26E1583000A61879 /* LoadingScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingScreenViewModel.swift; sourceTree = "<group>"; };
0D8A43C3272AEEDE005A6414 /* SecantTextStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantTextStyles.swift; sourceTree = "<group>"; };
0D8A43C5272B129C005A6414 /* WordChipGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordChipGrid.swift; sourceTree = "<group>"; };
0DA13C8D26C15D1D00E3B610 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
0DA13C8E26C15D1D00E3B610 /* WelcomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenViewModel.swift; sourceTree = "<group>"; };
0DA13C9526C186FF00E3B610 /* RestoreWalletScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreWalletScreen.swift; sourceTree = "<group>"; };
@ -199,6 +210,7 @@
0DB8AA80271DC7520035BC9D /* DesignGuide.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignGuide.swift; sourceTree = "<group>"; };
0DF2DC50272344E400FA31E2 /* EmptyChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyChip.swift; sourceTree = "<group>"; };
0DF2DC5327235E3E00FA31E2 /* View+InnerShadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+InnerShadow.swift"; sourceTree = "<group>"; };
0DFE93DE272C6D4B000FCCA5 /* RecoveryFlowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryFlowTests.swift; sourceTree = "<group>"; };
2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHeaderView.swift; sourceTree = "<group>"; };
2E5C03802738C570008BFFD3 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = "<group>"; };
2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFooterView.swift; sourceTree = "<group>"; };
@ -271,14 +283,14 @@
path = Routers;
sourceTree = "<group>";
};
0D170A7426BC9B7500EB6A46 /* Mocked Dependencies */ = {
0D170A7426BC9B7500EB6A46 /* MockedDependencies */ = {
isa = PBXGroup;
children = (
0D354A0726D5A9D000315F45 /* KeyStoring.swift */,
0D354A0826D5A9D000315F45 /* MnemonicSeedPhraseHandling.swift */,
0D354A0626D5A9D000315F45 /* Services.swift */,
);
path = "Mocked Dependencies";
path = MockedDependencies;
sourceTree = "<group>";
};
0D1922E826BDD95000052649 /* Base */ = {
@ -383,6 +395,24 @@
path = Balance;
sourceTree = "<group>";
};
0D3D04052728B2D70032ABC1 /* BackupFlow */ = {
isa = PBXGroup;
children = (
0D3D04062728B2EC0032ABC1 /* Views */,
0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */,
);
path = BackupFlow;
sourceTree = "<group>";
};
0D3D04062728B2EC0032ABC1 /* Views */ = {
isa = PBXGroup;
children = (
0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */,
0D8A43C5272B129C005A6414 /* WordChipGrid.swift */,
);
path = Views;
sourceTree = "<group>";
};
0D4E79FC26B364170058B01E = {
isa = PBXGroup;
children = (
@ -410,14 +440,14 @@
0DACFA7D27208CC80039EEA5 /* Util */,
6654C73B2715A3F000901167 /* Features */,
660558F4270C85F7009D6954 /* Generated */,
0D5D16F326E24CB900AD33D1 /* App Errors */,
0D5D16F326E24CB900AD33D1 /* AppErrors */,
0D2ACE7E26C2C65E00D62E3C /* Fonts */,
0DA13CA326C1960A00E3B610 /* Models */,
0DA13C9126C15E1900E3B610 /* UI Components */,
0DA13C9126C15E1900E3B610 /* UIComponents */,
0D1922F026BDE27D00052649 /* Stubs */,
0D1922EB26BDD9A500052649 /* Screens */,
0D1922E826BDD95000052649 /* Base */,
0D170A7426BC9B7500EB6A46 /* Mocked Dependencies */,
0D170A7426BC9B7500EB6A46 /* MockedDependencies */,
0D4E7A0826B364170058B01E /* SecantApp.swift */,
0D4E7A0A26B364170058B01E /* ContentView.swift */,
0D4E7A0C26B364180058B01E /* Assets.xcassets */,
@ -440,6 +470,7 @@
0D4E7A1926B364180058B01E /* secantTests */ = {
isa = PBXGroup;
children = (
0DFE93DD272C6D4B000FCCA5 /* BackupFlowTests */,
6654C7422715A48E00901167 /* OnboardingTests */,
0D4E7A1A26B364180058B01E /* secantTests.swift */,
0D4E7A1C26B364180058B01E /* Info.plist */,
@ -477,12 +508,12 @@
path = Chips;
sourceTree = "<group>";
};
0D5D16F326E24CB900AD33D1 /* App Errors */ = {
0D5D16F326E24CB900AD33D1 /* AppErrors */ = {
isa = PBXGroup;
children = (
0D5D16F426E24CCF00AD33D1 /* AppError.swift */,
);
path = "App Errors";
path = AppErrors;
sourceTree = "<group>";
};
0D864A0626E154D100A61879 /* Error */ = {
@ -503,6 +534,14 @@
path = Loading;
sourceTree = "<group>";
};
0D8A43C2272AEEA7005A6414 /* FontStyles */ = {
isa = PBXGroup;
children = (
0D8A43C3272AEEDE005A6414 /* SecantTextStyles.swift */,
);
path = FontStyles;
sourceTree = "<group>";
};
0DA13C8C26C15CBE00E3B610 /* Welcome Screen */ = {
isa = PBXGroup;
children = (
@ -512,9 +551,10 @@
path = "Welcome Screen";
sourceTree = "<group>";
};
0DA13C9126C15E1900E3B610 /* UI Components */ = {
0DA13C9126C15E1900E3B610 /* UIComponents */ = {
isa = PBXGroup;
children = (
0D8A43C2272AEEA7005A6414 /* FontStyles */,
669FDAE7272C239D007B9422 /* CircularFrame */,
0DB8AA80271DC7520035BC9D /* DesignGuide.swift */,
0DF2DC5227235E1F00FA31E2 /* Extensions */,
@ -523,7 +563,7 @@
669FDAE5272C2371007B9422 /* ProgressIndicators */,
669FDAE6272C2380007B9422 /* Backgrounds */,
);
path = "UI Components";
path = UIComponents;
sourceTree = "<group>";
};
0DA13C9426C186B100E3B610 /* Restore Wallet */ = {
@ -602,6 +642,15 @@
path = Extensions;
sourceTree = "<group>";
};
0DFE93DD272C6D4B000FCCA5 /* BackupFlowTests */ = {
isa = PBXGroup;
children = (
0DFE93DE272C6D4B000FCCA5 /* RecoveryFlowTests.swift */,
0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */,
);
path = BackupFlowTests;
sourceTree = "<group>";
};
2E5C037F2738C55F008BFFD3 /* Onboarding */ = {
isa = PBXGroup;
children = (
@ -649,6 +698,7 @@
F93874EC273C4DE200F0E875 /* Home */,
F9C165B62740403600592F76 /* Send */,
F96B41E2273B501F0021B49A /* TransactionHistory */,
0D3D04052728B2D70032ABC1 /* BackupFlow */,
6654C73C2715A3FA00901167 /* Onboarding */,
);
path = Features;
@ -967,6 +1017,7 @@
0D32282D26C5870B00262533 /* SendScreen.swift in Sources */,
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
0D1922ED26BDE0C600052649 /* AppRouter.swift in Sources */,
0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */,
0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */,
0DA13C9D26C1942100E3B610 /* BackupWalletScreenViewModel.swift in Sources */,
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
@ -989,11 +1040,13 @@
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */,
F93874F1273C4DE200F0E875 /* HomeView.swift in Sources */,
0D32282326C586A800262533 /* HistoryScreen.swift in Sources */,
0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */,
0D864A0A26E154FD00A61879 /* InitFailedScreenViewModel.swift in Sources */,
0DA13CA526C1963000E3B610 /* Balance.swift in Sources */,
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */,
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */,
0D1922F826BDEB3500052649 /* MockServices.swift in Sources */,
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */,
0D4E7A0B26B364170058B01E /* ContentView.swift in Sources */,
0D170A7226BC802800EB6A46 /* Router.swift in Sources */,
0D354A0926D5A9D000315F45 /* Services.swift in Sources */,
@ -1018,6 +1071,7 @@
0DA13CA126C1955600E3B610 /* HomeScreen.swift in Sources */,
0DA13C9026C15D1D00E3B610 /* WelcomeScreenViewModel.swift in Sources */,
2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */,
0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */,
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */,
663FAB9E271D875700E495F8 /* CreateButton.swift in Sources */,
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */,
@ -1042,8 +1096,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0DFE93DF272C6D4B000FCCA5 /* RecoveryFlowTests.swift in Sources */,
0D864A0526E1546000A61879 /* LoadingScreenTests.swift in Sources */,
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1194,9 +1250,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = RLPRR8CPQG;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = secant/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@ -1218,9 +1274,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = RLPRR8CPQG;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = secant/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;

View File

@ -7,6 +7,6 @@
import Foundation
enum AppError: Error {
case failedToInitialize(Error)
enum AppError: Error, Equatable {
case failedToInitialize
}

View File

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

View File

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x57",
"green" : "0x33",
"red" : "0x26"
"blue" : "0.000",
"green" : "0.725",
"red" : "1.000"
}
},
"idiom" : "universal"

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
"blue" : "0.341",
"green" : "0.200",
"red" : "0.149"
}
},
"idiom" : "universal"

View File

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
"blue" : "0.333",
"green" : "0.192",
"red" : "0.141"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.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

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x41",
"green" : "0x23",
"red" : "0x0F"
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"

View File

@ -19,6 +19,15 @@
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.725",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],

View File

@ -0,0 +1,133 @@
//
// RecoveryPhraseDisplayStore.swift
// secant-testnet
//
// Created by Francisco Gindre on 10/26/21.
//
import Foundation
import ComposableArchitecture
import UIKit
enum RecoveryPhraseError: Error {
/// This error is thrown then the Recovery Phrase can't be generated
case unableToGeneratePhrase
}
struct Pasteboard {
let setString: (String) -> Void
let getString: () -> String?
}
extension Pasteboard {
private struct TestPasteboard {
static var general = TestPasteboard()
var string: String?
}
static let live = Pasteboard(
setString: { UIPasteboard.general.string = $0 },
getString: { UIPasteboard.general.string }
)
static let test = Pasteboard(
setString: { TestPasteboard.general.string = $0 },
getString: { TestPasteboard.general.string }
)
}
struct BackupPhraseEnvironment {
let mainQueue: AnySchedulerOf<DispatchQueue>
let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError>
let pasteboard: Pasteboard
}
extension BackupPhraseEnvironment {
private struct DemoPasteboard {
static var general = Self()
var string: String?
}
static let demo = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
newPhrase: { Effect(value: .init(words: RecoveryPhrase.demo.words)) },
pasteboard: .test
)
static let live = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
newPhrase: { Effect(value: .init(words: RecoveryPhrase.demo.words)) },
pasteboard: .live
)
}
typealias RecoveryPhraseDisplayStore = Store<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction>
struct RecoveryPhrase: Equatable {
struct Chunk: Hashable {
var startIndex: Int
var words: [String]
}
let words: [String]
private let chunkSize = 6
func toChunks() -> [Chunk] {
let chunks = words.count / chunkSize
return zip(0 ..< chunks, words.chunked(into: chunkSize)).map {
Chunk(startIndex: $0 * chunkSize + 1, words: $1)
}
}
func toString() -> String {
words.joined(separator: " ")
}
}
struct RecoveryPhraseDisplayState: Equatable {
var phrase: RecoveryPhrase?
var showCopyToBufferAlert = false
}
enum RecoveryPhraseDisplayAction: Equatable {
case createPhrase
case copyToBufferPressed
case finishedPressed
case phraseResponse(Result<RecoveryPhrase, RecoveryPhraseError>)
}
typealias RecoveryPhraseDisplayReducer = Reducer<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction, BackupPhraseEnvironment>
extension RecoveryPhraseDisplayReducer {
static let `default` = RecoveryPhraseDisplayReducer { state, action, environment in
switch action {
case .createPhrase:
return environment.newPhrase()
.receive(on: environment.mainQueue)
.catchToEffect(RecoveryPhraseDisplayAction.phraseResponse)
case .copyToBufferPressed:
guard let phrase = state.phrase?.toString() else { return .none }
environment.pasteboard.setString(phrase)
state.showCopyToBufferAlert = true
return .none
case .finishedPressed:
// TODO: remove this when feature is implemented in https://github.com/zcash/secant-ios-wallet/issues/47
return .none
case let .phraseResponse(.success(phrase)):
state.phrase = phrase
return .none
case .phraseResponse(.failure):
// TODO: remove this when feature is implemented in https://github.com/zcash/secant-ios-wallet/issues/129
return .none
}
}
}
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}

View File

@ -0,0 +1,119 @@
//
// RecoveryPhraseDisplayView.swift
// secant-testnet
//
// Created by Francisco Gindre on 10/26/21.
//
import SwiftUI
import ComposableArchitecture
struct RecoveryPhraseDisplayView: View {
let store: RecoveryPhraseDisplayStore
var body: some View {
WithViewStore(self.store) { viewStore in
ScrollView {
VStack {
if let chunks = viewStore.phrase?.toChunks() {
VStack(spacing: 20) {
Text("Your Secret Recovery Phrase")
.titleText()
.multilineTextAlignment(.center)
VStack(alignment: .leading, spacing: 4) {
Text("The following 24 words represent your funds and the security used to protect them.")
.bodyText()
Text("Back them up now! There will be a test.")
.bodyText()
}
}
VStack(alignment: .leading, spacing: 20) {
ForEach(chunks, id: \.startIndex) { chunk in
WordChipGrid(words: chunk.words, startingAt: chunk.startIndex)
}
}
VStack {
Button(
action: { viewStore.send(.finishedPressed) },
label: { Text("Finished!") }
)
.activeButtonStyle
.frame(height: 60)
Button(
action: {
viewStore.send(.copyToBufferPressed)
},
label: {
Text("Copy To Buffer")
.bodyText()
}
)
.frame(height: 60)
}
.padding()
} else {
Text("Oops no words")
}
}
.padding()
}
.padding(.horizontal)
}
// TODO: NavigationBar Style
.navigationBarTitleDisplayMode(.inline)
.applyScreenBackground()
}
}
// TODO: This should have a #DEBUG tag, but if so, it's not possible to compile this on release mode and submit it to testflight
extension RecoveryPhraseDisplayStore {
static var demo: RecoveryPhraseDisplayStore {
RecoveryPhraseDisplayStore(
initialState: .init(phrase: .demo),
reducer: .default,
environment: .demo
)
}
}
// TODO: This should have a #DEBUG tag, but if so, it's not possible to compile this on release mode and submit it to testflight
extension RecoveryPhrase {
static let testPhrase = [
// 1
"bring", "salute", "thank",
"require", "spirit", "toe",
// 7
"boil", "hill", "casino",
"trophy", "drink", "frown",
// 13
"bird", "grit", "close",
"morning", "bind", "cancel",
// 19
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
static let demo = RecoveryPhrase(words: testPhrase)
static let empty = RecoveryPhrase(words: [])
}
struct RecoveryPhraseDisplayView_Previews: PreviewProvider {
static let scheduler = DispatchQueue.main
static let store = RecoveryPhraseDisplayStore.demo
static var previews: some View {
NavigationView {
RecoveryPhraseDisplayView(store: store)
}
NavigationView {
RecoveryPhraseDisplayView(store: store)
}
.preferredColorScheme(.dark)
}
}

View File

@ -0,0 +1,75 @@
//
// WordChipGrid.swift
// secant-testnet
//
// Created by Francisco Gindre on 10/28/21.
//
import SwiftUI
/**
A 3x2 grid of numbered or empty chips.
*/
struct WordChipGrid: View {
static let spacing: CGFloat = 10
var chips: [PhraseChip.Kind]
var threeColumnGrid = Array(
repeating: GridItem(
.flexible(minimum: 60, maximum: 120),
spacing: Self.spacing,
alignment: .topLeading
),
count: 3
)
var body: some View {
LazyVGrid(
columns: threeColumnGrid,
alignment: .leading,
spacing: Self.spacing
) {
ForEach(chips, id: \.self) { wordChip in
chipView(for: wordChip)
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 30
)
}
}
}
init(words: [String], startingAt index: Int) {
self.chips = zip(words, index..<index + words.count).map({ word, index in
word.isEmpty ? .empty : .ordered(position: index, word: word)
})
}
@ViewBuilder func chipView(for chipKind: PhraseChip.Kind) -> some View {
switch chipKind {
case .empty:
EmptyChip()
case let .ordered(position, word):
EnumeratedChip(index: position, text: word)
case .unassigned(let word):
BlueChip(word: word)
}
}
}
struct WordChipGrid_Previews: PreviewProvider {
private static var words = [
"pyramid", "negative", "page",
"crown", "", "zebra"
]
static var previews: some View {
VStack {
WordChipGrid(words: words, startingAt: 1)
}
.padding()
}
}

View File

@ -6,6 +6,7 @@ struct HomeState: Equatable {
case history
case send
case onboarding
case recoveryPhraseDisplay
}
var transactionHistoryState: TransactionHistoryState
var route: Route?
@ -91,6 +92,15 @@ extension HomeViewStore {
)
}
var showPhraseDisplayBinding: Binding<Bool> {
self.binding(
get: { $0.route == .recoveryPhraseDisplay },
send: { isActive in
return .updateRoute(isActive ? .send : nil)
}
)
}
var showSendBinding: Binding<Bool> {
self.binding(
get: { $0.route == .send },
@ -102,7 +112,7 @@ extension HomeViewStore {
var showOnboardingBinding: Binding<Bool> {
self.binding(
get: {$0.route == .onboarding },
get: { $0.route == .onboarding },
send: { isActive in
return .updateRoute(isActive ? .onboarding : nil)
}

View File

@ -21,6 +21,13 @@ struct HomeView: View {
.primaryButtonStyle
.frame(height: 50)
Button(
action: { viewStore.send(.updateRoute(.recoveryPhraseDisplay)) },
label: { Text("Show Recovery Phrase Demo") }
)
.primaryButtonStyle
.frame(height: 50)
Button(
action: { viewStore.send(.updateRoute(.send)) },
label: { Text("Go to Send") }
@ -53,6 +60,12 @@ struct HomeView: View {
}
.padding(.horizontal, 30)
.navigationBarTitle("Home", displayMode: .inline)
.navigationLinkEmpty(
isActive: viewStore.showPhraseDisplayBinding,
destination: {
RecoveryPhraseDisplayView(store: .demo)
}
)
.navigationLinkEmpty(
isActive: viewStore.showSendBinding,
destination: {
@ -100,9 +113,6 @@ struct HomeView: View {
}
}
// MARK: - Previews
#if DEBUG
extension HomeStore {
static var demo: HomeStore {
HomeStore(
@ -118,7 +128,8 @@ extension HomeStore {
)
}
}
#endif
// MARK: - Previews
struct HomeView_Previews: PreviewProvider {
static var previews: some View {

View File

@ -11,7 +11,7 @@ enum SendAction: Equatable {
case updateRoute(SendView.Route?)
}
// Mark: - SendReducer
// MARK: - SendReducer
typealias SendReducer = Reducer<SendState, SendAction, Void>
@ -41,16 +41,15 @@ extension SendReducer {
}
}
// Mark: - SendStore
// MARK: - SendStore
typealias SendStore = Store<SendState, SendAction>
// Mark: - SendViewStore
// MARK: - SendViewStore
typealias SendViewStore = ViewStore<SendState, SendAction>
extension SendViewStore {
var bindingForTransaction: Binding<Transaction> {
self.binding(
get: \.transaction,
@ -86,4 +85,3 @@ extension SendViewStore {
)
}
}

View File

@ -17,7 +17,6 @@ struct Sent: View {
.frame(height: 50)
.padding()
Text("\(String(dumping: transaction))")
Text("\(String(dumping: isComplete))")

View File

@ -9,15 +9,6 @@ struct TransactionDetailView: View {
}
}
struct TransactionDetail_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TransactionDetailView(transaction: .demo)
}
}
}
#if DEBUG
extension Transaction {
static var demo: Self {
.init(
@ -29,4 +20,13 @@ extension Transaction {
)
}
}
#if DEBUG
struct TransactionDetail_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TransactionDetailView(transaction: .demo)
}
}
}
#endif

View File

@ -22,21 +22,6 @@ struct TransactionHistoryView: View {
}
}
struct TransactionView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TransactionHistoryView(store: .demo)
.navigationBarTitleDisplayMode(.inline)
}
NavigationView {
TransactionHistoryView(store: .demoWithSelectedTransaction)
.navigationBarTitleDisplayMode(.inline)
}
}
}
#if DEBUG
extension TransactionHistoryStore {
static var demo: Store<TransactionHistoryState, TransactionHistoryAction> {
return Store(
@ -77,4 +62,17 @@ extension IdentifiedArrayOf where Element == Transaction {
)
}
}
#endif
struct TransactionView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TransactionHistoryView(store: .demo)
.navigationBarTitleDisplayMode(.inline)
}
NavigationView {
TransactionHistoryView(store: .demoWithSelectedTransaction)
.navigationBarTitleDisplayMode(.inline)
}
}
}

View File

@ -38,6 +38,7 @@ internal enum Asset {
}
internal enum Colors {
internal enum BackgroundColors {
internal static let numberedChip = ColorAsset(name: "numberedChip")
internal static let phraseGridDarkGray = ColorAsset(name: "phraseGridDarkGray")
}
internal enum Buttons {
@ -73,6 +74,7 @@ internal enum Asset {
}
internal enum Text {
internal static let activeButtonText = ColorAsset(name: "ActiveButtonText")
internal static let body = ColorAsset(name: "Body")
internal static let button = ColorAsset(name: "Button")
internal static let heading = ColorAsset(name: "Heading")
internal static let medium = ColorAsset(name: "Medium")

View File

@ -25,6 +25,8 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIAppFonts</key>

View File

@ -15,7 +15,7 @@ struct SecantApp: App {
NavigationView {
HomeView(store: homeStore)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationViewStyle(StackNavigationViewStyle())
}
}
}

View File

@ -45,7 +45,7 @@ struct ButtonModifier_Previews: PreviewProvider {
Button("Example Button") { dump("Example button") }
.exampleButtonStyle
.padding(.horizontal, 25)
.frame(height: 75)
.frame(height: 60)
.previewLayout(.fixed(width: 300, height: 100))
.preferredColorScheme(.dark)
}

View File

@ -19,14 +19,14 @@ struct EnumeratedChip: View {
minWidth: 0,
maxWidth: .infinity,
minHeight: 30,
idealHeight: 40,
maxHeight: .infinity,
alignment: .leading
)
.padding(.horizontal, 16)
.padding(.leading, 14)
.padding(.vertical, 4)
.background(Asset.Colors.Buttons.primaryButtonPressed.color)
.background(Asset.Colors.BackgroundColors.numberedChip.color)
.cornerRadius(6)
.shadow(color: Asset.Colors.Shadow.numberedTextShadow.color, radius: 3, x: 0, y: 1)
}
}
@ -35,24 +35,30 @@ struct NumberedText: View {
var text: String
@ViewBuilder var numberedText: some View {
Text(number.superscriptRepresentation)
GeometryReader { geometry in
(Text("\(number)")
.baselineOffset(geometry.size.height / 4)
.foregroundColor(Asset.Colors.Text.highlightedSuperscriptText.color)
.font(.custom(FontFamily.Roboto.bold.name, size: 20)) +
.font(.custom(FontFamily.Roboto.bold.name, size: 12)) +
Text(" \(text)")
.foregroundColor(Asset.Colors.Text.button.color)
.font(.custom(FontFamily.Rubik.medium.name, size: 16))
}
var body: some View {
numberedText
.font(.custom(FontFamily.Rubik.regular.name, size: 14))
)
.shadow(
color: Asset.Colors.Shadow.numberedTextShadow.color,
radius: 1,
x: 0,
y: 1
)
.fixedSize(horizontal: false, vertical: true)
.frame(height: geometry.size.height, alignment: .center)
}
}
var body: some View {
numberedText
.layoutPriority(1)
.fixedSize(horizontal: false, vertical: false)
}
}
@ -83,8 +89,8 @@ struct EnumeratedChip_Previews: PreviewProvider {
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 30,
idealHeight: 40
minHeight: 40,
maxHeight: .infinity
)
} else {
EnumeratedChip(index: (i + 1), text: word)
@ -92,7 +98,7 @@ struct EnumeratedChip_Previews: PreviewProvider {
minWidth: 0,
maxWidth: .infinity,
minHeight: 30,
idealHeight: 40
maxHeight: .infinity
)
}
}

View File

@ -8,7 +8,7 @@
import SwiftUI
struct PhraseChip: View {
enum Kind {
enum Kind: Hashable {
case empty
case unassigned(word: String)
case ordered(position: Int, word: String)

View File

@ -0,0 +1,35 @@
//
// SecantTextStyles.swift
// secant-testnet
//
// Created by Francisco Gindre on 10/28/21.
//
import Foundation
import SwiftUI
extension Text {
func bodyText() -> some View {
self.modifier(BodyTextStyle())
}
func titleText() -> some View {
self.modifier(TitleTextStyle())
}
/// Body text style. Used for content. Roboto-Regular 18pt
private struct BodyTextStyle: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(Asset.Colors.Text.body.color)
.font(.custom(FontFamily.Rubik.regular.name, size: 18))
}
}
private struct TitleTextStyle: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(Asset.Colors.Text.body.color)
.font(.custom(FontFamily.Roboto.medium.name, size: 24))
}
}
}

View File

@ -1,6 +1,6 @@
import SwiftUI
#if DEBUG
// TODO: This should have a #DEBUG tag, but if so, it's not possible to compile this on release mode and submit it to testflight
struct StateContainer<T, Content: View>: View {
@State private var state: T
private var content: (Binding<T>) -> Content
@ -14,4 +14,3 @@ struct StateContainer<T, Content: View>: View {
content($state)
}
}
#endif

View File

@ -1,6 +1,6 @@
import Foundation
#if DEBUG
// TODO: This should have a #DEBUG tag, but if so, it's not possible to compile this on release mode and submit it to testflight
extension String {
init<T>(dumping value: T) {
var output = String()
@ -8,4 +8,3 @@ extension String {
self.init(stringLiteral: output)
}
}
#endif

View File

@ -1,4 +1,5 @@
import SwiftUI
import ComposableArchitecture
struct WithStateBinding<T: Equatable, Content: View>: View {
@State var localState: T

View File

@ -0,0 +1,37 @@
//
// RecoveryFlowTests.swift
// secantTests
//
// Created by Francisco Gindre on 10/29/21.
//
import XCTest
@testable import secant_testnet
class RecoveryFlowTests: XCTestCase {
func testGiven24WordsBIP39ChunkItIntoQuarters() throws {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let phrase = RecoveryPhrase(words: words)
let chunks = phrase.toChunks()
XCTAssertEqual(chunks.count, 4)
XCTAssertEqual(chunks[0].startIndex, 1)
XCTAssertEqual(chunks[0].words, ["bring", "salute", "thank", "require", "spirit", "toe"])
XCTAssertEqual(chunks[1].startIndex, 7)
XCTAssertEqual(chunks[2].startIndex, 13)
XCTAssertEqual(chunks[3].startIndex, 19)
}
}

View File

@ -0,0 +1,55 @@
//
// RecoveryPhraseDisplayStoreTests.swift
// secantTests
//
// Created by Francisco Gindre on 12/8/21.
//
import XCTest
import ComposableArchitecture
@testable import secant_testnet
class RecoveryPhraseDisplayReducerTests: XCTestCase {
func testCopyToBuffer() {
let store = TestStore(
initialState: .test,
reducer: .default,
environment: .demo
)
store.send(.copyToBufferPressed) {
$0.phrase = .demo
$0.showCopyToBufferAlert = true
}
XCTAssertEqual(
store.environment.pasteboard.getString(),
RecoveryPhrase.demo.toString()
)
}
func testNewPhrase() {
let store = TestStore(
initialState: .empty,
reducer: .default,
environment: .demo
)
store.send(.phraseResponse(.success(.demo))) {
$0.phrase = .demo
$0.showCopyToBufferAlert = false
}
}
}
private extension RecoveryPhraseDisplayState {
static let test = RecoveryPhraseDisplayState(
phrase: .demo,
showCopyToBufferAlert: false
)
static let empty = RecoveryPhraseDisplayState(
phrase: .empty,
showCopyToBufferAlert: false
)
}