First Stab at The Composable Architecture

Initial Light Mode Recovery Phrase Backup Screen

Dark mode color adjustments

Quotes "Navigation Bar Title"

Fix Recovery Phrase chips throw a UI Thread warning

Integrate Copy to buffer button

create test build 4 for UX validation

FIX: Archiving compilation errors

fix warnings. add TODOs for demo code that is needed to build on release mode

PR Fixes: remove dump calles add TODOs

remove unneeded padding

remove prints

PR fixes: remove min height from standard button style.

change extension from View to Text

change comment format

PR Fixes. code style

Add ticket number to TODOs

Rename "Backup Flow" to BackupFlow

PR lint fixes

Add tests

Fix lint issue. cleanup

rename "UI Components" to "UIComponents"

Renamed folder with spaces to CamelCase names:
"App Errors" to "AppErrors"
"Mocked Dependencies" to "MockedDependencies"

Renamed "Font Styles" to "FontStyles"

hook up to home screen

Adding: [Suggestion(adding pasteboard to environment)]

Implement [Suggestion(use specific RecoveryPhraseError) | non-blocking]

Remove double carriage return and replace by VStack of Text()

add App Uses Non-Exempt Encryption -> NO value to Info.plist

bump build #

0.0.1-7

make view modifiers private

move modifiers into extension

Testable Pasteboard

Fix: Word groups don't have noticeable spacing that allows the user to tell them apert

FIX: don't truncate enumerated chips
This commit is contained in:
Francisco Gindre 2021-10-26 20:14:03 -03:00
parent 0500f5db68
commit 5646afbdd5
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
)
}