diff --git a/mobile-app/assets/high_security/security_icon_big.svg b/mobile-app/assets/high_security/security_icon_big.svg
new file mode 100644
index 00000000..592ccaae
--- /dev/null
+++ b/mobile-app/assets/high_security/security_icon_big.svg
@@ -0,0 +1,21 @@
+
diff --git a/mobile-app/assets/high_security/security_icon_black.svg b/mobile-app/assets/high_security/security_icon_black.svg
new file mode 100644
index 00000000..ee793ee2
--- /dev/null
+++ b/mobile-app/assets/high_security/security_icon_black.svg
@@ -0,0 +1,7 @@
+
diff --git a/mobile-app/assets/high_security/step_indicator_active_icon.svg b/mobile-app/assets/high_security/step_indicator_active_icon.svg
new file mode 100644
index 00000000..1d151160
--- /dev/null
+++ b/mobile-app/assets/high_security/step_indicator_active_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/mobile-app/assets/high_security/step_indicator_icon.svg b/mobile-app/assets/high_security/step_indicator_icon.svg
new file mode 100644
index 00000000..7ea01eef
--- /dev/null
+++ b/mobile-app/assets/high_security/step_indicator_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/mobile-app/ios/Runner.xcodeproj/project.pbxproj b/mobile-app/ios/Runner.xcodeproj/project.pbxproj
index f3a23933..eb4370d4 100644
--- a/mobile-app/ios/Runner.xcodeproj/project.pbxproj
+++ b/mobile-app/ios/Runner.xcodeproj/project.pbxproj
@@ -8,14 +8,14 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 193D36486744F9CA896B5858 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABCD771D31123455D6A0A380 /* Pods_RunnerTests.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 86E110147A80CB996E2CA4AB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97939A145D33F6801E304DF5 /* Pods_Runner.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
- A7A4D6A980D319AC470A9637 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313CD1C8F586AE6AE7FC4933 /* Pods_Runner.framework */; };
+ F2AFF251EE4997924A7D0F78 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ABF2C48D8CF924735FEFF77 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -42,30 +42,30 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 0E7E3C1546B23A5AFF5942FD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 0ABF2C48D8CF924735FEFF77 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 313CD1C8F586AE6AE7FC4933 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 19F520CF0B8598B21F21E35D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ 1F5D52D89FF5F3FC074A093C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
4672F8772DB9DA61003B0FFF /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
- 4F6FA13E483D97F15F98587C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 91653C25A16732D6D1699E5A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ 823E822652161FDACC420786 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97939A145D33F6801E304DF5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- ABCD771D31123455D6A0A380 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- BFE1153F1F57BEE110B11642 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
- C4D362161EB554F367F7E9E7 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
- E3E5F71FCCC8D2959D796E5E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ CDD18E53B3D0BB011AFA8C44 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ CE44BF236E1BD0C36A568AA1 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ DD6CCDCA2672B6A4F381839C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -73,7 +73,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 193D36486744F9CA896B5858 /* Pods_RunnerTests.framework in Frameworks */,
+ F2AFF251EE4997924A7D0F78 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -81,7 +81,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- A7A4D6A980D319AC470A9637 /* Pods_Runner.framework in Frameworks */,
+ 86E110147A80CB996E2CA4AB /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -96,11 +96,11 @@
path = RunnerTests;
sourceTree = "";
};
- 58644EB040BB5BEDAFF2CD5F /* Frameworks */ = {
+ 568D30F7218BAC6CA6F26C90 /* Frameworks */ = {
isa = PBXGroup;
children = (
- 313CD1C8F586AE6AE7FC4933 /* Pods_Runner.framework */,
- ABCD771D31123455D6A0A380 /* Pods_RunnerTests.framework */,
+ 97939A145D33F6801E304DF5 /* Pods_Runner.framework */,
+ 0ABF2C48D8CF924735FEFF77 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -108,12 +108,12 @@
697901FF368C5DA3A4CA46A0 /* Pods */ = {
isa = PBXGroup;
children = (
- 0E7E3C1546B23A5AFF5942FD /* Pods-Runner.debug.xcconfig */,
- 4F6FA13E483D97F15F98587C /* Pods-Runner.release.xcconfig */,
- E3E5F71FCCC8D2959D796E5E /* Pods-Runner.profile.xcconfig */,
- BFE1153F1F57BEE110B11642 /* Pods-RunnerTests.debug.xcconfig */,
- 91653C25A16732D6D1699E5A /* Pods-RunnerTests.release.xcconfig */,
- C4D362161EB554F367F7E9E7 /* Pods-RunnerTests.profile.xcconfig */,
+ 823E822652161FDACC420786 /* Pods-Runner.debug.xcconfig */,
+ 1F5D52D89FF5F3FC074A093C /* Pods-Runner.release.xcconfig */,
+ CDD18E53B3D0BB011AFA8C44 /* Pods-Runner.profile.xcconfig */,
+ DD6CCDCA2672B6A4F381839C /* Pods-RunnerTests.debug.xcconfig */,
+ CE44BF236E1BD0C36A568AA1 /* Pods-RunnerTests.release.xcconfig */,
+ 19F520CF0B8598B21F21E35D /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -137,7 +137,7 @@
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
697901FF368C5DA3A4CA46A0 /* Pods */,
- 58644EB040BB5BEDAFF2CD5F /* Frameworks */,
+ 568D30F7218BAC6CA6F26C90 /* Frameworks */,
);
sourceTree = "";
};
@@ -173,7 +173,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- 005E706ADC7DC70A01BC32C5 /* [CP] Check Pods Manifest.lock */,
+ BF3E28A893917F2766F78391 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
83542EEFCC0959E7B844E8CA /* Frameworks */,
@@ -192,14 +192,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 48B9C5DA509103240CAD37F8 /* [CP] Check Pods Manifest.lock */,
+ EC24DA874FC9ADBA74522F88 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- CA35B1F2B3EDD6A977D48ECC /* [CP] Embed Pods Frameworks */,
+ E9E213D6C6EC5BE831912A8A /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -271,45 +271,38 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 005E706ADC7DC70A01BC32C5 /* [CP] Check Pods Manifest.lock */ = {
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
- inputFileListPaths = (
- );
inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
+ name = "Thin Binary";
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
- "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
- name = "Thin Binary";
+ name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
- 48B9C5DA509103240CAD37F8 /* [CP] Check Pods Manifest.lock */ = {
+ BF3E28A893917F2766F78391 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -324,43 +317,50 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 9740EEB61CF901F6004384FC /* Run Script */ = {
+ E9E213D6C6EC5BE831912A8A /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
- inputPaths = (
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "Run Script";
- outputPaths = (
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
};
- CA35B1F2B3EDD6A977D48ECC /* [CP] Embed Pods Frameworks */ = {
+ EC24DA874FC9ADBA74522F88 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "[CP] Embed Pods Frameworks";
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@@ -498,7 +498,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = BFE1153F1F57BEE110B11642 /* Pods-RunnerTests.debug.xcconfig */;
+ baseConfigurationReference = DD6CCDCA2672B6A4F381839C /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -517,7 +517,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 91653C25A16732D6D1699E5A /* Pods-RunnerTests.release.xcconfig */;
+ baseConfigurationReference = CE44BF236E1BD0C36A568AA1 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -534,7 +534,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = C4D362161EB554F367F7E9E7 /* Pods-RunnerTests.profile.xcconfig */;
+ baseConfigurationReference = 19F520CF0B8598B21F21E35D /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
diff --git a/mobile-app/lib/features/components/custom_text_field.dart b/mobile-app/lib/features/components/custom_text_field.dart
index 10a66b94..02e360e8 100644
--- a/mobile-app/lib/features/components/custom_text_field.dart
+++ b/mobile-app/lib/features/components/custom_text_field.dart
@@ -4,6 +4,8 @@ import 'package:resonance_network_wallet/features/components/label.dart';
import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+enum TextFieldVariant { primary, secondary }
+
class CustomTextField extends StatelessWidget {
final String? labelText;
final TextStyle? textStyle;
@@ -19,6 +21,7 @@ class CustomTextField extends StatelessWidget {
final String? errorMsg;
final double? leftPadding;
final bool? disabled;
+ final TextFieldVariant variant;
const CustomTextField({
super.key,
@@ -36,10 +39,18 @@ class CustomTextField extends StatelessWidget {
this.leftPadding,
this.controller,
this.disabled = false,
+ this.variant = TextFieldVariant.primary,
}) : assert(initialValue == null || controller == null, 'Cannot provide both an initialValue and a controller.');
@override
Widget build(BuildContext context) {
+ final effectiveTextStyle = variant == TextFieldVariant.primary
+ ? context.themeText.smallTitle
+ : context.themeText.paragraph;
+ final effectiveHintStyle = variant == TextFieldVariant.primary
+ ? context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5))
+ : context.themeText.paragraph?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5));
+
// The main container for the entire widget
return SizedBox(
width: double.infinity,
@@ -60,7 +71,7 @@ class CustomTextField extends StatelessWidget {
onChanged: onChanged,
obscureText: obscureText,
// Styling for the text inside the input field
- style: textStyle ?? context.themeText.smallTitle,
+ style: textStyle ?? effectiveTextStyle,
decoration: InputDecoration(
fillColor: fillColor,
isDense: true, // Reduces vertical padding
@@ -78,9 +89,7 @@ class CustomTextField extends StatelessWidget {
), // Removes default padding
hintText: hintText,
// Style for the hint text when the field is empty
- hintStyle:
- hintStyle ??
- context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5)),
+ hintStyle: hintStyle ?? effectiveHintStyle,
),
),
diff --git a/mobile-app/lib/features/components/gradient_text.dart b/mobile-app/lib/features/components/gradient_text.dart
new file mode 100644
index 00000000..eead202e
--- /dev/null
+++ b/mobile-app/lib/features/components/gradient_text.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+
+class GradientText extends StatelessWidget {
+ final String text;
+ final List colors;
+ final TextStyle? style;
+
+ const GradientText(this.text, {super.key, required this.colors, required this.style});
+
+ @override
+ Widget build(BuildContext context) {
+ return ShaderMask(
+ shaderCallback: (bounds) => LinearGradient(
+ colors: colors,
+ begin: const Alignment(0.00, -1.00),
+ end: const Alignment(0, 1),
+ ).createShader(bounds),
+ child: Text(text, style: style?.copyWith(color: Colors.white)),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/components/steps.dart b/mobile-app/lib/features/components/steps.dart
new file mode 100644
index 00000000..1053d4c0
--- /dev/null
+++ b/mobile-app/lib/features/components/steps.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+
+class StepsIndicator extends StatelessWidget {
+ final int currentStep;
+ final int totalSteps;
+ final double lineHeight = 2;
+ final double iconHeight = 6.56;
+
+ const StepsIndicator({super.key, required this.currentStep, required this.totalSteps})
+ : assert(currentStep >= 1 && currentStep <= totalSteps);
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ SizedBox(
+ height: iconHeight,
+ child: Row(
+ children: List.generate(totalSteps, (index) {
+ return Expanded(
+ child: Row(
+ children: [if (index < totalSteps) _buildStepLine(context, index), _buildStepPoint(context, index)],
+ ),
+ );
+ }),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildStepPoint(BuildContext context, int index) {
+ final isCompleted = index < currentStep - 1;
+ final isCurrent = index == currentStep - 1;
+ final iconPath = (isCompleted || isCurrent)
+ ? 'assets/high_security/step_indicator_active_icon.svg'
+ : 'assets/high_security/step_indicator_icon.svg';
+
+ return Center(child: SvgPicture.asset(iconPath, width: 4, height: iconHeight));
+ }
+
+ Widget _buildStepLine(BuildContext context, int index) {
+ final isCompleted = index < currentStep - 1;
+ final isCurrent = index == currentStep - 1;
+ final lineColor = (isCompleted || isCurrent) ? context.themeColors.checksum : const Color(0x66FFFFFF);
+
+ return Expanded(
+ child: Container(height: lineHeight, color: lineColor),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/components/tree_list.dart b/mobile-app/lib/features/components/tree_list.dart
new file mode 100644
index 00000000..535111fe
--- /dev/null
+++ b/mobile-app/lib/features/components/tree_list.dart
@@ -0,0 +1,358 @@
+import 'package:flutter/material.dart';
+
+// Data model for tree nodes
+class TreeNode {
+ final T data;
+ final List> children;
+ bool isExpanded;
+
+ TreeNode({required this.data, this.children = const [], this.isExpanded = true});
+
+ bool get hasChildren => children.isNotEmpty;
+ bool get isLeaf => children.isEmpty;
+}
+
+// Tree structure list widget
+class TreeListView extends StatefulWidget {
+ final List> nodes;
+ final Widget Function(BuildContext context, TreeNode node, int depth) nodeBuilder;
+
+ final bool showExpandCollapse;
+ final EdgeInsetsGeometry? padding;
+ final ScrollPhysics? physics;
+ final bool shrinkWrap;
+
+ const TreeListView({
+ super.key,
+ required this.nodes,
+ required this.nodeBuilder,
+ this.showExpandCollapse = true,
+ this.padding,
+ this.physics,
+ this.shrinkWrap = false,
+ });
+
+ @override
+ State> createState() => _TreeListViewState();
+}
+
+class _TreeListViewState extends State> {
+ final Color lineColor = const Color(0x66FFFFFF);
+ final double lineWidth = 1.0;
+ final double indentWidth = 24.0;
+
+ @override
+ Widget build(BuildContext context) {
+ return ListView(
+ padding: widget.padding,
+ physics: widget.physics,
+ shrinkWrap: widget.shrinkWrap,
+ children: _buildTreeNodes(widget.nodes, 0, []),
+ );
+ }
+
+ List _buildTreeNodes(List> nodes, int depth, List parentLines) {
+ List widgets = [];
+
+ for (int i = 0; i < nodes.length; i++) {
+ final node = nodes[i];
+ final isLast = i == nodes.length - 1;
+ final currentParentLines = List.from(parentLines)..add(!isLast);
+
+ widgets.add(
+ _TreeNodeWidget(
+ node: node,
+ depth: depth,
+ isLast: isLast,
+ parentLines: parentLines,
+ indentWidth: indentWidth,
+ lineColor: lineColor,
+ lineWidth: lineWidth,
+ showExpandCollapse: widget.showExpandCollapse,
+ nodeBuilder: widget.nodeBuilder,
+ onToggleExpanded: () {
+ setState(() {
+ node.isExpanded = !node.isExpanded;
+ });
+ },
+ ),
+ );
+
+ if (node.hasChildren && node.isExpanded) {
+ widgets.addAll(_buildTreeNodes(node.children, depth + 1, currentParentLines));
+ }
+ }
+
+ return widgets;
+ }
+}
+
+class _TreeNodeWidget extends StatelessWidget {
+ final TreeNode node;
+ final int depth;
+ final bool isLast;
+ final List parentLines;
+ final double indentWidth;
+ final Color lineColor;
+ final double lineWidth;
+ final bool showExpandCollapse;
+ final Widget Function(BuildContext context, TreeNode node, int depth) nodeBuilder;
+ final VoidCallback onToggleExpanded;
+
+ const _TreeNodeWidget({
+ required this.node,
+ required this.depth,
+ required this.isLast,
+ required this.parentLines,
+ required this.indentWidth,
+ required this.lineColor,
+ required this.lineWidth,
+ required this.showExpandCollapse,
+ required this.nodeBuilder,
+ required this.onToggleExpanded,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ children: [
+ // Tree lines
+ SizedBox(
+ width: (depth + 1) * indentWidth,
+ height: 56,
+ child: CustomPaint(
+ painter: TreeLinePainter(
+ depth: depth,
+ isLast: isLast,
+ parentLines: parentLines,
+ lineColor: lineColor,
+ lineWidth: lineWidth,
+ indentWidth: indentWidth,
+ ),
+ ),
+ ),
+ // Expand/collapse button
+ if (showExpandCollapse && node.hasChildren)
+ GestureDetector(
+ onTap: onToggleExpanded,
+ child: Container(
+ width: 20,
+ height: 20,
+ margin: const EdgeInsets.only(right: 4),
+ decoration: BoxDecoration(
+ border: Border.all(color: lineColor),
+ color: Colors.white,
+ ),
+ child: Icon(node.isExpanded ? Icons.remove : Icons.add, size: 12, color: lineColor),
+ ),
+ )
+ else if (showExpandCollapse)
+ const SizedBox(width: 24),
+ // Node content
+ Expanded(child: nodeBuilder(context, node, depth)),
+ ],
+ );
+ }
+}
+
+class TreeLinePainter extends CustomPainter {
+ final int depth;
+ final bool isLast;
+ final List parentLines;
+ final Color lineColor;
+ final double lineWidth;
+ final double indentWidth;
+
+ TreeLinePainter({
+ required this.depth,
+ required this.isLast,
+ required this.parentLines,
+ required this.lineColor,
+ required this.lineWidth,
+ required this.indentWidth,
+ });
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ final paint = Paint()
+ ..color = lineColor
+ ..strokeWidth = lineWidth
+ ..strokeCap = StrokeCap.round
+ ..strokeJoin = StrokeJoin.round;
+
+ // Draw vertical lines for parent levels
+ for (int i = 0; i < parentLines.length; i++) {
+ if (parentLines[i]) {
+ final x = (i + 1) * indentWidth - indentWidth / 2;
+ canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
+ }
+ }
+
+ if (depth >= 0) {
+ final x = (depth + 1) * indentWidth - indentWidth / 2;
+ final centerY = size.height / 2;
+
+ // Draw vertical line (up to center or full height)
+ if (!isLast) {
+ canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
+ } else {
+ canvas.drawLine(Offset(x, 0), Offset(x, centerY), paint);
+ }
+
+ // Draw horizontal line to the node (shorter to make room for arrow)
+ final horizontalEndX = x + indentWidth / 2 - 6; // Leave space for arrow
+ canvas.drawLine(Offset(x, centerY), Offset(horizontalEndX, centerY), paint);
+
+ // Draw arrow at the end of horizontal line
+ _drawArrow(canvas, paint, Offset(horizontalEndX, centerY));
+ }
+ }
+
+ void _drawArrow(Canvas canvas, Paint paint, Offset position) {
+ final arrowSize = 5.0;
+
+ // Draw horizontal line for arrow shaft
+ canvas.drawLine(Offset(position.dx, position.dy), Offset(position.dx + arrowSize, position.dy), paint);
+
+ // Draw arrow head (two diagonal lines forming >)
+ canvas.drawLine(
+ Offset(position.dx + arrowSize, position.dy),
+ Offset(position.dx + arrowSize - 2, position.dy - 2),
+ paint,
+ );
+ canvas.drawLine(
+ Offset(position.dx + arrowSize, position.dy),
+ Offset(position.dx + arrowSize - 2, position.dy + 2),
+ paint,
+ );
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
+}
+
+// Simple tree builder utility functions
+List> buildTreeFromMap(Map data, T Function(String key, dynamic value) converter) {
+ List> nodes = [];
+
+ data.forEach((key, value) {
+ if (value is Map) {
+ nodes.add(TreeNode(data: converter(key, value), children: buildTreeFromMap(value, converter)));
+ } else if (value is List) {
+ nodes.add(
+ TreeNode(
+ data: converter(key, value),
+ children: value.map>((item) => TreeNode(data: converter(item.toString(), item))).toList(),
+ ),
+ );
+ } else {
+ nodes.add(TreeNode(data: converter(key, value)));
+ }
+ });
+
+ return nodes;
+}
+
+// File system item for demo
+class FileSystemItem {
+ final String name;
+ final bool isDirectory;
+ final String? extension;
+
+ FileSystemItem({required this.name, required this.isDirectory, this.extension});
+
+ IconData get icon {
+ if (isDirectory) return Icons.folder;
+ switch (extension?.toLowerCase()) {
+ case 'dart':
+ return Icons.code;
+ case 'md':
+ return Icons.description;
+ case 'json':
+ return Icons.data_object;
+ case 'yaml':
+ case 'yml':
+ return Icons.settings;
+ default:
+ return Icons.insert_drive_file;
+ }
+ }
+
+ Color get iconColor {
+ if (isDirectory) return Colors.blue;
+ switch (extension?.toLowerCase()) {
+ case 'dart':
+ return Colors.blue;
+ case 'md':
+ return Colors.orange;
+ case 'json':
+ return Colors.green;
+ case 'yaml':
+ case 'yml':
+ return Colors.purple;
+ default:
+ return Colors.grey;
+ }
+ }
+}
+
+// Demo widget
+class TreeListViewDemo extends StatelessWidget {
+ const TreeListViewDemo({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final fileSystem = [
+ TreeNode(data: FileSystemItem(name: 'lib', isDirectory: true)),
+ TreeNode(data: FileSystemItem(name: 'assets', isDirectory: true)),
+ TreeNode(
+ data: FileSystemItem(name: 'pubspec.yaml', isDirectory: false, extension: 'yaml'),
+ ),
+ TreeNode(
+ data: FileSystemItem(name: 'README.md', isDirectory: false, extension: 'md'),
+ ),
+ ];
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Tree Structure List'),
+ backgroundColor: Colors.blue,
+ foregroundColor: Colors.white,
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('File System Tree:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
+ const SizedBox(height: 16),
+ Expanded(
+ child: TreeListView(
+ showExpandCollapse: false,
+ nodes: fileSystem,
+ nodeBuilder: (context, node, depth) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 2),
+ child: Row(
+ children: [
+ Icon(node.data.icon, size: 18, color: node.data.iconColor),
+ const SizedBox(width: 8),
+ Text(
+ node.data.name,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight: node.data.isDirectory ? FontWeight.w500 : FontWeight.normal,
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/components/wallet_action_button.dart b/mobile-app/lib/features/components/wallet_action_button.dart
new file mode 100644
index 00000000..912a1079
--- /dev/null
+++ b/mobile-app/lib/features/components/wallet_action_button.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+
+class WalletActionButton extends StatelessWidget {
+ final String assetPath;
+ const WalletActionButton({super.key, required this.assetPath});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.all(4),
+ decoration: ShapeDecoration(
+ color: Colors.white.useOpacity(0.15),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
+ ),
+ child: SvgPicture.asset(
+ assetPath,
+ width: context.themeSize.mainMenuIconSize,
+ height: context.themeSize.mainMenuIconSize,
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/components/wallet_app_bar.dart b/mobile-app/lib/features/components/wallet_app_bar.dart
index d877722c..bfdcc71a 100644
--- a/mobile-app/lib/features/components/wallet_app_bar.dart
+++ b/mobile-app/lib/features/components/wallet_app_bar.dart
@@ -10,6 +10,9 @@ abstract class WalletAppBar extends StatelessWidget implements PreferredSizeWidg
factory WalletAppBar.simple({Key? key, required String title}) => _SimpleWalletAppBar(key: key, title: title);
+ factory WalletAppBar.simpleWithBackButton({Key? key, required String title, VoidCallback? onBack}) =>
+ _StandardWalletAppBar(key: key, title: title, onBack: onBack);
+
factory WalletAppBar.custom({Key? key, required Widget titleWidget, Widget? leadingWidget, List? actions}) =>
_CustomWalletAppBar(key: key, titleWidget: titleWidget, leadingWidget: leadingWidget, actions: actions);
diff --git a/mobile-app/lib/features/main/screens/account_settings_screen.dart b/mobile-app/lib/features/main/screens/account_settings_screen.dart
index e9db52c0..14f3fa69 100644
--- a/mobile-app/lib/features/main/screens/account_settings_screen.dart
+++ b/mobile-app/lib/features/main/screens/account_settings_screen.dart
@@ -16,11 +16,13 @@ import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
import 'package:resonance_network_wallet/features/components/sphere.dart';
import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart';
import 'package:resonance_network_wallet/features/main/screens/create_account_screen.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_get_started_screen.dart';
import 'package:resonance_network_wallet/features/main/screens/receive_screen.dart';
import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart';
import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart';
+import 'package:resonance_network_wallet/utils/feature_flags.dart';
class AccountSettingsScreen extends ConsumerStatefulWidget {
final Account account;
@@ -165,10 +167,11 @@ class _AccountSettingsScreenState extends ConsumerState {
_buildShareSection(),
const SizedBox(height: 20),
_buildAddressSection(),
- const SizedBox(height: 20),
- _buildSecuritySection(),
- const SizedBox(height: 20),
- if (widget.account.accountType == AccountType.keystone) _buildDisconnectWalletButton(),
+ if (FeatureFlags.enableHighSecurity) ...[const SizedBox(height: 20), _buildSecuritySection()],
+ if (widget.account.accountType == AccountType.keystone) ...[
+ const SizedBox(height: 20),
+ _buildDisconnectWalletButton(),
+ ],
const SizedBox(height: 30),
],
),
@@ -276,25 +279,24 @@ class _AccountSettingsScreenState extends ConsumerState {
Widget _buildSecuritySection() {
return _buildSettingCard(
- child: Padding(
- padding: const EdgeInsets.only(top: 12.0, left: 12.0, bottom: 12.0, right: 26.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Row(
- children: [
- SvgPicture.asset('assets/high_security_icon.svg', width: context.isTablet ? 28 : 20),
- const SizedBox(width: 12),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('High Security', style: context.themeText.largeTag),
- Text('COMING SOON', style: context.themeText.detail?.copyWith(color: context.themeColors.checksum)),
- ],
- ),
- ],
- ),
- ],
+ child: InkWell(
+ onTap: () {
+ Navigator.push(context, MaterialPageRoute(builder: (context) => const HighSecurityGetStartedScreen()));
+ },
+ child: Padding(
+ padding: const EdgeInsets.only(top: 12.0, left: 12.0, bottom: 12.0, right: 26.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ SvgPicture.asset('assets/high_security_icon.svg', width: context.isTablet ? 28 : 20),
+ const SizedBox(width: 12),
+ Text('High Security', style: context.themeText.largeTag),
+ ],
+ ),
+ ],
+ ),
),
),
);
diff --git a/mobile-app/lib/features/main/screens/accounts_screen.dart b/mobile-app/lib/features/main/screens/accounts_screen.dart
index e3a13691..26a8356c 100644
--- a/mobile-app/lib/features/main/screens/accounts_screen.dart
+++ b/mobile-app/lib/features/main/screens/accounts_screen.dart
@@ -8,6 +8,7 @@ import 'package:resonance_network_wallet/features/components/scaffold_base.dart'
import 'package:resonance_network_wallet/features/components/select.dart';
import 'package:resonance_network_wallet/features/components/select_action_sheet.dart';
import 'package:resonance_network_wallet/features/components/sphere.dart';
+import 'package:resonance_network_wallet/features/components/tree_list.dart';
import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart';
import 'package:resonance_network_wallet/features/main/screens/account_settings_screen.dart';
import 'package:resonance_network_wallet/features/main/screens/add_hardware_account_screen.dart';
@@ -18,9 +19,11 @@ import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
import 'package:resonance_network_wallet/providers/account_providers.dart';
+import 'package:resonance_network_wallet/providers/entrusted_account_provider.dart';
import 'package:resonance_network_wallet/providers/wallet_providers.dart';
import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart';
import 'package:resonance_network_wallet/utils/feature_flags.dart';
+import 'dart:math';
enum _WalletMoreAction { createWallet, importWallet, addHardwareWallet }
@@ -109,7 +112,7 @@ class _AccountsScreenState extends ConsumerState {
Item(value: _WalletMoreAction.importWallet, label: 'Import wallet'),
];
- if (FeatureFlags.showKeystoneHardwareWallet) {
+ if (FeatureFlags.enableKeystoneHardwareWallet) {
items.add(Item(value: _WalletMoreAction.addHardwareWallet, label: 'Add hardware wallet'));
}
@@ -267,6 +270,13 @@ class _AccountsScreenState extends ConsumerState {
}
Widget _buildAccountListItem(Account account, bool isActive, int index) {
+ final entrustedAccountsAsync = ref.watch(entrustedAccountsProvider(account));
+ final entrustedAccountsData = entrustedAccountsAsync.value ?? [];
+
+ final entrustedNodes = entrustedAccountsData.map((entrusted) => TreeNode(data: entrusted)).toList();
+
+ final double constraintMaxHeight = min(entrustedNodes.length * 52, 104);
+
return InkWell(
onTap: () async {
await ref.read(activeAccountProvider.notifier).setActiveAccount(account);
@@ -275,153 +285,234 @@ class _AccountsScreenState extends ConsumerState {
child: Stack(
clipBehavior: Clip.hardEdge,
children: [
- Row(
+ Column(
children: [
- Expanded(
- child: Container(
- padding: EdgeInsets.symmetric(horizontal: context.isTablet ? 20 : 8, vertical: 8),
- decoration: ShapeDecoration(
- color: isActive ? context.themeColors.surfaceActive : context.themeColors.surface,
- shape: RoundedRectangleBorder(
- side: BorderSide(width: 1, color: context.themeColors.borderLight),
- borderRadius: BorderRadius.circular(5),
- ),
- ),
- child: Row(
- children: [
- const SizedBox(width: 24),
- Expanded(
- child: Consumer(
- builder: (context, ref, child) {
- final balanceAsync = ref.watch(balanceProviderFamily(account.accountId));
-
- return FutureBuilder(
- future: _checksumService.getHumanReadableName(account.accountId),
- builder: (context, checksumSnapshot) {
- final humanChecksum = checksumSnapshot.data ?? '';
-
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- account.name,
- style: context.themeText.paragraph?.copyWith(
- color: isActive ? Colors.black : Colors.white,
- ),
- ),
- Text(
- humanChecksum,
- style: context.themeText.detail?.copyWith(
- color: isActive
- ? context.themeColors.checksum
- : context.themeColors.checksumDarker,
- ),
- ),
- Row(
+ Row(
+ children: [
+ Expanded(
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: context.isTablet ? 20 : 8, vertical: 8),
+ height: context.themeSize.accountListItemHeight,
+ decoration: ShapeDecoration(
+ color: isActive ? context.themeColors.surfaceActive : context.themeColors.surface,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(width: 1, color: context.themeColors.borderLight),
+ borderRadius: BorderRadius.circular(5),
+ ),
+ ),
+ child: Row(
+ children: [
+ const SizedBox(width: 24),
+ Expanded(
+ child: Consumer(
+ builder: (context, ref, child) {
+ final balanceAsync = ref.watch(balanceProviderFamily(account.accountId));
+
+ return FutureBuilder(
+ future: _checksumService.getHumanReadableName(account.accountId),
+ builder: (context, checksumSnapshot) {
+ final humanChecksum = checksumSnapshot.data ?? '';
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ account.name,
+ style: context.themeText.smallParagraph?.copyWith(
+ color: isActive ? Colors.black : Colors.white,
+ ),
+ ),
+ if (entrustedNodes.isNotEmpty)
+ const AccountTag('Guardian', color: Color(0xFF9747FF)),
+ ],
+ ),
Text(
- context.isTablet
- ? account.accountId
- // ignore: lines_longer_than_80_chars
- : AddressFormattingService.formatAddress(account.accountId),
+ humanChecksum,
style: context.themeText.detail?.copyWith(
color: isActive
- ? context.themeColors.darkGray
- : context.themeColors.textMuted,
+ ? context.themeColors.checksumDarker
+ : context.themeColors.checksum,
),
),
- ],
- ),
- const SizedBox(height: 2),
- balanceAsync.when(
- loading: () => Text(
- 'loading balance...',
- style: context.themeText.detail?.copyWith(
- color: isActive ? context.themeColors.darkGray : context.themeColors.light,
- ),
- ),
- error: (error, _) => Text(
- 'error loading',
- style: context.themeText.detail?.copyWith(
- color: isActive
- ? context.themeColors.darkGray
- : context.themeColors.textPrimary,
- ),
- ),
- data: (balance) => Text.rich(
- TextSpan(
+ Row(
children: [
- TextSpan(
- text: _formattingService.formatBalance(balance),
- style: context.themeText.smallParagraph?.copyWith(
+ Text(
+ context.isTablet
+ ? account.accountId
+ // ignore: lines_longer_than_80_chars
+ : AddressFormattingService.formatAddress(account.accountId),
+ style: context.themeText.tiny?.copyWith(
color: isActive
? context.themeColors.darkGray
- : context.themeColors.textPrimary,
+ : context.themeColors.textMuted,
),
),
+ ],
+ ),
+ const SizedBox(height: 2),
+ balanceAsync.when(
+ loading: () => Text(
+ 'loading balance...',
+ style: context.themeText.detail?.copyWith(
+ color: isActive
+ ? context.themeColors.darkGray
+ : context.themeColors.light,
+ ),
+ ),
+ error: (error, _) => Text(
+ 'error loading',
+ style: context.themeText.detail?.copyWith(
+ color: isActive
+ ? context.themeColors.darkGray
+ : context.themeColors.textPrimary,
+ ),
+ ),
+ data: (balance) => Text.rich(
TextSpan(
- text: ' ${AppConstants.tokenSymbol}',
- style: context.themeText.detail?.copyWith(
- color: isActive
- ? context.themeColors.darkGray
- : context.themeColors.textPrimary,
- ),
+ children: [
+ TextSpan(
+ text: _formattingService.formatBalance(balance),
+ style: context.themeText.detail?.copyWith(
+ color: isActive
+ ? context.themeColors.darkGray
+ : context.themeColors.textPrimary,
+ ),
+ ),
+ TextSpan(
+ text: ' ${AppConstants.tokenSymbol}',
+ style: context.themeText.tiny?.copyWith(
+ color: isActive
+ ? context.themeColors.darkGray
+ : context.themeColors.textPrimary,
+ ),
+ ),
+ ],
),
- ],
+ ),
),
- ),
- ),
- ],
+ ],
+ );
+ },
);
},
- );
- },
- ),
+ ),
+ ),
+ ],
),
- ],
+ ),
),
- ),
- ),
- IconButton(
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(),
- icon: SvgPicture.asset(
- 'assets/settings_icon_off.svg',
- width: context.isTablet ? 28 : 21,
- colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
- ),
- onPressed: () async {
- // Get current data from providers
- final balanceAsync = ref.read(balanceProviderFamily(account.accountId));
- final checksumName = await _checksumService.getHumanReadableName(account.accountId);
-
- balanceAsync.when(
- loading: () {
- // Show loading or handle appropriately
- },
- error: (error, _) {
- // Handle error
+ IconButton(
+ padding: EdgeInsets.zero,
+ constraints: const BoxConstraints(),
+ icon: SvgPicture.asset(
+ 'assets/settings_icon_off.svg',
+ width: context.isTablet ? 28 : 21,
+ colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
+ ),
+ onPressed: () async {
+ // Get current data from providers
+ final balanceAsync = ref.read(balanceProviderFamily(account.accountId));
+ final checksumName = await _checksumService.getHumanReadableName(account.accountId);
+
+ balanceAsync.when(
+ loading: () {
+ // Show loading or handle appropriately
+ },
+ error: (error, _) {
+ // Handle error
+ },
+ data: (balance) async {
+ if (!mounted) return;
+ await Navigator.push(
+ context,
+ MaterialPageRoute(
+ settings: const RouteSettings(name: AppConstants.accountSettingsRouteName),
+ builder: (context) => AccountSettingsScreen(
+ account: account,
+ balance: _formattingService.formatBalance(balance, addSymbol: true),
+ checksumName: checksumName,
+ ),
+ ),
+ );
+ // Providers will automatically refresh if needed
+ },
+ );
},
- data: (balance) async {
- if (!mounted) return;
- await Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => AccountSettingsScreen(
- account: account,
- balance: _formattingService.formatBalance(balance, addSymbol: true),
- checksumName: checksumName,
+ ),
+ ],
+ ),
+ if (entrustedNodes.isNotEmpty)
+ ConstrainedBox(
+ constraints: BoxConstraints(maxHeight: constraintMaxHeight),
+ child: TreeListView(
+ showExpandCollapse: false,
+ nodes: entrustedNodes,
+ nodeBuilder: (context, node, depth) {
+ final entrusted = node.data;
+ return Row(
+ children: [
+ Expanded(
+ child: Container(
+ decoration: ShapeDecoration(
+ color: context.themeColors.darkGray,
+ shape: RoundedRectangleBorder(
+ side: const BorderSide(color: Color(0x26FFFFFF)),
+ borderRadius: BorderRadius.circular(5),
+ ),
+ ),
+ padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(entrusted.name, style: context.themeText.smallParagraph),
+ const AccountTag('Entrusted'),
+ ],
+ ),
+ ),
),
- ),
+ IconButton(
+ padding: EdgeInsets.zero,
+ constraints: const BoxConstraints(),
+ icon: SvgPicture.asset(
+ 'assets/settings_icon_off.svg',
+ width: context.isTablet ? 28 : 21,
+ colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
+ ),
+ onPressed: () async {
+ // Get current data from providers
+ final balanceAsync = ref.read(balanceProviderFamily(entrusted.accountId));
+ final checksumName = await _checksumService.getHumanReadableName(entrusted.accountId);
+
+ balanceAsync.when(
+ loading: () {},
+ error: (error, _) {},
+ data: (balance) async {
+ if (!mounted) return;
+ await Navigator.push(
+ context,
+ MaterialPageRoute(
+ settings: const RouteSettings(name: AppConstants.accountSettingsRouteName),
+ builder: (context) => AccountSettingsScreen(
+ account: entrusted,
+ balance: _formattingService.formatBalance(balance, addSymbol: true),
+ checksumName: checksumName,
+ ),
+ ),
+ );
+ },
+ );
+ },
+ ),
+ ],
);
- // Providers will automatically refresh if needed
},
- );
- },
- ),
+ ),
+ ),
],
),
-
Positioned(
// calculating the middle point
top: (context.themeSize.accountListItemHeight / 2) - (context.themeSize.accountListItemLogoWidth / 2),
@@ -437,3 +528,25 @@ class _AccountsScreenState extends ConsumerState {
);
}
}
+
+class AccountTag extends StatelessWidget {
+ final String label;
+ final Color? color;
+
+ const AccountTag(this.label, {super.key, this.color});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
+ decoration: BoxDecoration(
+ color: color ?? const Color(0xFFFFD541), // Default to Entrusted color (Yellow-ish)
+ borderRadius: BorderRadius.circular(4),
+ ),
+ child: Text(
+ label,
+ style: context.themeText.tiny?.copyWith(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 10),
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart b/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart
new file mode 100644
index 00000000..ab41e26b
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart
@@ -0,0 +1,136 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart';
+
+class GuardianAccountInfoSheet extends StatefulWidget {
+ const GuardianAccountInfoSheet({super.key});
+
+ @override
+ State createState() => _GuardianAccountInfoSheetState();
+}
+
+class _GuardianAccountInfoSheetState extends State {
+ void _closeSheet() {
+ Navigator.pop(context);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ bottom: false,
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16),
+ decoration: ShapeDecoration(
+ color: context.themeColors.background,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(7),
+ decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ InkWell(
+ onTap: _closeSheet,
+ child: Icon(Icons.close, size: context.isTablet ? 28 : 24),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 24),
+ Row(
+ children: [
+ Icon(Icons.info_outline, size: context.themeSize.infoSheetTitleIcon),
+ const SizedBox(width: 22),
+ Text('What is a Guardian Account', style: context.themeText.largeTag),
+ ],
+ ),
+ const SizedBox(height: 22),
+ Text(
+ 'A Guardian account acts as a secure backstop. It intercepts transactions by diverting funds to itself if the Entrusted account (this account) is compromised. The Guardian account can be owned by you or a trusted 3rd party.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 16),
+ SizedBox(
+ width: double.infinity,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('A Guardian account can:', style: context.themeText.smallParagraph),
+ Text('• Intercept any transaction', style: context.themeText.smallParagraph),
+ Text('• Pull all funds from this account', style: context.themeText.smallParagraph),
+ Text('• Change the recovery address', style: context.themeText.smallParagraph),
+ ],
+ ),
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'The Guardian account should not be in the same wallet as the Entrusted account as in the case of theft both would be exposed.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'The harder the Guardian account is to access, the higher the security. An account on a cold storage wallet is the most secure.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 40),
+ Button(
+ variant: ButtonVariant.primary,
+ label: 'Got it!',
+ onPressed: () {
+ _closeSheet();
+ },
+ ),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+// Helper function to show the receive sheet
+void showGuardianAccountInfoSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ backgroundColor: Colors.transparent,
+ isScrollControlled: true,
+ constraints: BoxConstraints(
+ maxWidth: MediaQuery.of(context).size.width, // Ensure full width
+ ),
+ builder: (context) => Stack(
+ children: [
+ Positioned.fill(
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black],
+ ),
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
+ child: Container(color: Colors.black.useOpacity(0.3), child: const GuardianAccountInfoSheet()),
+ ),
+ ),
+ ],
+ ),
+ );
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart
new file mode 100644
index 00000000..454c150a
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart
@@ -0,0 +1,132 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart';
+
+class HighSecurityCancelWarningSheet extends StatefulWidget {
+ const HighSecurityCancelWarningSheet({super.key});
+
+ @override
+ State createState() => _HighSecurityCancelWarningSheetState();
+}
+
+class _HighSecurityCancelWarningSheetState extends State {
+ void _returnToAccountSetting() {
+ if (!mounted) return;
+ Navigator.of(context).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName));
+ }
+
+ void _continueSetup() {
+ Navigator.pop(context);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ bottom: false,
+ child: Container(
+ height: MediaQuery.of(context).size.height * 0.8,
+ padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16),
+ decoration: ShapeDecoration(
+ color: context.themeColors.background,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+ ),
+ child: Column(
+ children: [
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(7),
+ decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ InkWell(
+ onTap: _continueSetup,
+ child: Icon(Icons.close, size: context.isTablet ? 28 : 24),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 69),
+ Text(
+ 'Are you sure you want to exit High Security Setup?',
+ textAlign: TextAlign.center,
+ style: context.themeText.smallTitle,
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'Your preferences will be discarded',
+ textAlign: TextAlign.center,
+ style: context.themeText.smallTitle,
+ ),
+ const SizedBox(height: 69),
+ Row(
+ spacing: context.themeSize.buttonsHorizontalSpacing,
+ children: [
+ Expanded(
+ child: Button(
+ variant: ButtonVariant.danger,
+ label: 'Exit anyway',
+ textStyle: context.themeText.smallParagraph?.copyWith(fontWeight: FontWeight.w600),
+ onPressed: () {
+ _returnToAccountSetting();
+ },
+ ),
+ ),
+ Expanded(
+ child: Button(
+ variant: ButtonVariant.neutral,
+ label: 'Continue',
+ textStyle: context.themeText.smallParagraph?.copyWith(fontWeight: FontWeight.w600),
+ onPressed: _continueSetup,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+void showHighSecurityCancelWarningSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ backgroundColor: Colors.transparent,
+ isScrollControlled: true,
+ constraints: BoxConstraints(
+ maxWidth: MediaQuery.of(context).size.width, // Ensure full width
+ ),
+ builder: (context) => Stack(
+ children: [
+ Positioned.fill(
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black],
+ ),
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
+ child: Container(color: Colors.black.useOpacity(0.3), child: const HighSecurityCancelWarningSheet()),
+ ),
+ ),
+ ],
+ ),
+ );
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart
new file mode 100644
index 00000000..f4ffceda
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart
@@ -0,0 +1,195 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_cancel_warning_sheet.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_created_sheet.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/providers/high_security_form_provider.dart';
+import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart';
+
+class HighSecurityConfirmationSheet extends ConsumerStatefulWidget {
+ const HighSecurityConfirmationSheet({super.key});
+
+ @override
+ ConsumerState createState() => _HighSecurityConfirmationSheetState();
+}
+
+class _HighSecurityConfirmationSheetState extends ConsumerState {
+ final HighSecurityService _highSecurityService = HighSecurityService();
+ final SettingsService _settingsService = SettingsService();
+
+ BigInt? _networkFee;
+ bool _isSubmitting = false;
+
+ void _confirmSetup() async {
+ setState(() {
+ _isSubmitting = true;
+ });
+
+ final formData = ref.read(highSecurityFormProvider);
+ final activeAccount = (await _settingsService.getActiveAccount())!;
+
+ await _highSecurityService.setupHighSecurityAccount(activeAccount, formData);
+
+ setState(() {
+ _isSubmitting = false;
+ });
+
+ if (mounted) {
+ showHighSecurityCreatedSheet(context);
+ }
+ }
+
+ void _cancelSetup() {
+ showHighSecurityCancelWarningSheet(context);
+ }
+
+ void _fetchNetworkFee() async {
+ final activeAccount = (await _settingsService.getActiveAccount())!;
+ final formData = ref.read(highSecurityFormProvider);
+
+ final fee = await _highSecurityService.getHighSecuritySetupFee(activeAccount, formData);
+
+ setState(() {
+ _networkFee = fee.fee;
+ });
+ }
+
+ @override
+ void initState() {
+ super.initState();
+
+ _fetchNetworkFee();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ bottom: false,
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16),
+ decoration: ShapeDecoration(
+ color: context.themeColors.background,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(7),
+ decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ InkWell(
+ onTap: _cancelSetup,
+ child: Icon(Icons.close, size: context.isTablet ? 28 : 24),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 24),
+ Text('WARNING:', style: context.themeText.largeTitle?.copyWith(color: context.themeColors.buttonDanger)),
+ const SizedBox(height: 11),
+ Text(
+ 'These features are designed to help keep your funds safer, but once confirmed this account CANNOT:',
+ style: context.themeText.largeTag,
+ ),
+ const SizedBox(height: 11),
+ SizedBox(
+ width: double.infinity,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('• Turn off High Security', style: context.themeText.smallParagraph),
+ Text('• Reverse a transaction', style: context.themeText.smallParagraph),
+ Text('• Change the Guardian account', style: context.themeText.smallParagraph),
+ Text('• Change the Recovery account', style: context.themeText.smallParagraph),
+ Text('• Change the Safeguard window', style: context.themeText.smallParagraph),
+ Text('• Deny a recovery request', style: context.themeText.smallParagraph),
+ ],
+ ),
+ ),
+ const SizedBox(height: 11),
+ Text(
+ 'You will still be able to send and receive crypto and view requests.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 116),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text('Network Fee', style: context.themeText.detail?.copyWith(fontWeight: FontWeight.w600)),
+ Text(
+ '${_networkFee ?? 'Fetching...'} ${AppConstants.tokenSymbol}',
+ style: context.themeText.detail?.copyWith(fontWeight: FontWeight.w600),
+ ),
+ ],
+ ),
+ const SizedBox(height: 13),
+ Button(
+ isLoading: _isSubmitting,
+ variant: ButtonVariant.primary,
+ label: 'Confirm',
+ onPressed: () {
+ _confirmSetup();
+ },
+ ),
+ const SizedBox(height: 13),
+ Button(
+ isDisabled: _isSubmitting,
+ variant: ButtonVariant.dangerOutline,
+ label: 'Cancel',
+ onPressed: () {
+ _cancelSetup();
+ },
+ ),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+void showHighSecurityConfirmationSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ backgroundColor: Colors.transparent,
+ isScrollControlled: true,
+ constraints: BoxConstraints(
+ maxWidth: MediaQuery.of(context).size.width, // Ensure full width
+ ),
+ builder: (context) => Stack(
+ children: [
+ Positioned.fill(
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black],
+ ),
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
+ child: Container(color: Colors.black.useOpacity(0.3), child: const HighSecurityConfirmationSheet()),
+ ),
+ ),
+ ],
+ ),
+ );
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart
new file mode 100644
index 00000000..ea9fad0f
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart
@@ -0,0 +1,111 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart';
+
+class HighSecurityCreatedSheet extends StatefulWidget {
+ const HighSecurityCreatedSheet({super.key});
+
+ @override
+ State createState() => _HighSecurityCreatedSheetState();
+}
+
+class _HighSecurityCreatedSheetState extends State {
+ void _returnToAccountSetting() {
+ if (!mounted) return;
+ Navigator.of(context).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ bottom: false,
+ child: Container(
+ height: MediaQuery.of(context).size.height * 0.8,
+ padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16),
+ decoration: ShapeDecoration(
+ color: context.themeColors.background,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+ ),
+ child: Column(
+ children: [
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(7),
+ decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ InkWell(
+ onTap: _returnToAccountSetting,
+ child: Icon(Icons.close, size: context.isTablet ? 28 : 24),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 69),
+ SvgPicture.asset('assets/logo/logo.svg', width: 91, height: 85),
+ const SizedBox(height: 18),
+ Text('CONFIRMED', style: context.themeText.largeTitle),
+ const SizedBox(height: 46),
+ Text(
+ 'High Security has been successfully setup on this account',
+ style: context.themeText.largeTag,
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 46),
+ Button(variant: ButtonVariant.neutral, label: 'Done', width: 188, onPressed: _returnToAccountSetting),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+void showHighSecurityCreatedSheet(BuildContext context) {
+ void returnToAccountSetting() {
+ Navigator.of(context).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName));
+ }
+
+ showModalBottomSheet(
+ context: context,
+ backgroundColor: Colors.transparent,
+ isScrollControlled: true,
+ constraints: BoxConstraints(
+ maxWidth: MediaQuery.of(context).size.width, // Ensure full width
+ ),
+ builder: (context) => Stack(
+ children: [
+ Positioned.fill(
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black],
+ ),
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
+ child: Container(color: Colors.black.useOpacity(0.3), child: const HighSecurityCreatedSheet()),
+ ),
+ ),
+ ],
+ ),
+ ).whenComplete(() {
+ returnToAccountSetting();
+ });
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart
new file mode 100644
index 00000000..6d7cffdc
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/components/scaffold_base.dart';
+import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_guardian_wizard.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/providers/high_security_form_provider.dart';
+
+class HighSecurityGetStartedScreen extends ConsumerWidget {
+ const HighSecurityGetStartedScreen({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final formNotifier = ref.read(highSecurityFormProvider.notifier);
+
+ return ScaffoldBase(
+ appBar: WalletAppBar.simpleWithBackButton(title: 'Security Settings'),
+ child: Column(
+ children: [
+ const SizedBox(height: 73),
+ SvgPicture.asset('assets/high_security/security_icon_big.svg', width: 140, height: 175),
+ const SizedBox(height: 26),
+ Text('HIGH SECURITY', style: context.themeText.largeTitle),
+ const SizedBox(height: 25),
+ Text(
+ "Don't risk your funds!\nEnabling High Security is a great way to keep your money safe. But safety comes at the cost of convenience.",
+ textAlign: TextAlign.center,
+ style: context.themeText.paragraph,
+ ),
+ const SizedBox(height: 13),
+ Text(
+ 'Once you enable this feature it cannot be disabled',
+ textAlign: TextAlign.center,
+ style: context.themeText.paragraph?.copyWith(fontWeight: FontWeight.w600),
+ ),
+ const Expanded(child: SizedBox()),
+ Button(
+ variant: ButtonVariant.neutral,
+ label: 'Start',
+ onPressed: () {
+ formNotifier.resetState();
+ Navigator.push(context, MaterialPageRoute(builder: (context) => const HighSecurityGuardianWizard()));
+ },
+ ),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart
new file mode 100644
index 00000000..bf71b6b5
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart
@@ -0,0 +1,162 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/components/custom_text_field.dart';
+import 'package:resonance_network_wallet/features/components/gradient_text.dart';
+import 'package:resonance_network_wallet/features/components/scaffold_base.dart';
+import 'package:resonance_network_wallet/features/components/steps.dart';
+import 'package:resonance_network_wallet/features/components/wallet_action_button.dart';
+import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/guardian_account_info_sheet.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_safeguard_window_wizard.dart';
+import 'package:resonance_network_wallet/features/main/screens/send/qr_scanner_screen.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/providers/high_security_form_provider.dart';
+
+class HighSecurityGuardianWizard extends ConsumerStatefulWidget {
+ const HighSecurityGuardianWizard({super.key});
+
+ @override
+ ConsumerState createState() => _HighSecurityGuardianWizardState();
+}
+
+class _HighSecurityGuardianWizardState extends ConsumerState {
+ Future _scanQRCode() async {
+ final formNotifier = ref.read(highSecurityFormProvider.notifier);
+
+ final scannedAddress = await Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const QRScannerScreen(), fullscreenDialog: true),
+ );
+
+ if (scannedAddress != null && mounted) {
+ formNotifier.updateGuardianAddress(scannedAddress);
+ }
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final formNotifier = ref.read(highSecurityFormProvider.notifier);
+ final guardianAddress = ref.watch(highSecurityFormProvider).guardianAddress;
+
+ final bool isDisabled = guardianAddress.isEmpty;
+
+ return ScaffoldBase(
+ appBar: WalletAppBar.simpleWithBackButton(title: 'Theft Deterrence'),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 36),
+ const Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SizedBox(
+ width: 204,
+ child: StepsIndicator(currentStep: 1, totalSteps: AppConstants.highSecurityStepsCount),
+ ),
+ ],
+ ),
+ const SizedBox(height: 32),
+ GradientText('THEFT DETERRENCE', colors: context.themeColors.aquaBlue, style: context.themeText.largeTitle),
+ const SizedBox(height: 4),
+ Text(
+ 'Intercept any transaction or “pull” all funds in the case of theft.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 38),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text('Guardian Account', style: context.themeText.largeTag),
+ InkWell(
+ onTap: () {
+ showGuardianAccountInfoSheet(context);
+ },
+ child: const Icon(Icons.info_outline),
+ ),
+ ],
+ ),
+ const SizedBox(height: 4),
+ Text(
+ 'Choose an account that keeps your funds safe if your main wallet is compromised.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 13),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ GestureDetector(
+ onTap: () async {
+ final data = await Clipboard.getData('text/plain');
+ if (data != null && data.text != null) {
+ formNotifier.updateGuardianAddress(data.text!);
+ }
+ },
+ child: const WalletActionButton(assetPath: 'assets/paste_icon_1.svg'),
+ ),
+ const SizedBox(width: 12),
+ GestureDetector(
+ onTap: _scanQRCode,
+ child: const WalletActionButton(assetPath: 'assets/scan_1.svg'),
+ ),
+ ],
+ ),
+ const SizedBox(height: 13),
+ CustomTextField(
+ variant: TextFieldVariant.secondary,
+ initialValue: guardianAddress,
+ onChanged: formNotifier.updateGuardianAddress,
+ hintText: 'Enter address',
+ ),
+ const SizedBox(height: 13),
+ Text(
+ 'The harder the Guardian account is to access the higher the security. An address on a cold storage wallet is the most secure.',
+ style: context.themeText.smallParagraph?.copyWith(color: context.themeColors.textMuted),
+ ),
+ const Expanded(child: SizedBox()),
+ Row(
+ spacing: 36,
+ children: [
+ Expanded(
+ child: Button(
+ label: 'Back',
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ ),
+ Expanded(
+ child: Button(
+ isDisabled: isDisabled,
+ variant: ButtonVariant.neutral,
+ label: 'Next',
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const HighSecuritySafeguardWindowWizard()),
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart
new file mode 100644
index 00000000..03a41798
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart
@@ -0,0 +1,141 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/components/gradient_text.dart';
+import 'package:resonance_network_wallet/features/components/scaffold_base.dart';
+import 'package:resonance_network_wallet/features/components/steps.dart';
+import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_summary_wizard.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/safeguard_window_picker_sheet.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/providers/high_security_form_provider.dart';
+import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart';
+
+class HighSecuritySafeguardWindowWizard extends ConsumerStatefulWidget {
+ const HighSecuritySafeguardWindowWizard({super.key});
+
+ @override
+ ConsumerState createState() => _HighSecuritySafeguardWindowWizardState();
+}
+
+class _HighSecuritySafeguardWindowWizardState extends ConsumerState {
+ @override
+ Widget build(BuildContext context) {
+ final formNotifier = ref.read(highSecurityFormProvider.notifier);
+ final safeguardTimeSeconds = ref.watch(highSecurityFormProvider).safeguardWindow;
+
+ final int secondsInADay = 86400;
+ final int secondsInAMonth = secondsInADay * 30; // 86400 seconds/day * 30 days/month
+
+ /// This is an approximation.
+ final int safeguardTimeMonths = safeguardTimeSeconds ~/ secondsInAMonth;
+ final int safeguardTimeDays = (safeguardTimeSeconds % secondsInAMonth) ~/ secondsInADay;
+ final int safeguardTimeHours = (safeguardTimeSeconds % secondsInADay) ~/ 3600;
+
+ final bool isDisabled = safeguardTimeSeconds == 0;
+
+ return ScaffoldBase(
+ appBar: WalletAppBar.simpleWithBackButton(title: 'Safeguard Window'),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 36),
+ const Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SizedBox(
+ width: 204,
+ child: StepsIndicator(currentStep: 2, totalSteps: AppConstants.highSecurityStepsCount),
+ ),
+ ],
+ ),
+ const SizedBox(height: 32),
+ GradientText('SAFEGUARD WINDOW', colors: context.themeColors.aquaBlue, style: context.themeText.largeTitle),
+ const SizedBox(height: 4),
+ Text(
+ 'The time window in which the Guardian can deny or intercept a transaction.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 38),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [Text('Safeguard Window', style: context.themeText.largeTag)],
+ ),
+ const SizedBox(height: 4),
+ Text(
+ 'Set how long the Guardian account has to act once a transaction is initiated.',
+ style: context.themeText.smallParagraph,
+ ),
+ const SizedBox(height: 13),
+ GestureDetector(
+ onTap: () {
+ showSafeguardWindowPickerSheet(
+ context,
+ safeguardTimeMonths: safeguardTimeMonths,
+ safeguardTimeDays: safeguardTimeDays,
+ safeguardTimeHours: safeguardTimeHours,
+ setSafeguardTimeSeconds: formNotifier.updateSafeguardWindow,
+ );
+ },
+ child: Container(
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ decoration: ShapeDecoration(
+ color: const Color(0xFF313131),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ DatetimeFormattingService.formatSafeguardTime(
+ safeguardTimeMonths,
+ safeguardTimeDays,
+ safeguardTimeHours,
+ ),
+ style: context.themeText.smallParagraph,
+ ),
+ Icon(Icons.edit, color: Colors.white70, size: context.isTablet ? 22 : 14),
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(height: 13),
+ Text(
+ 'Allow a reasonable window for your Guardian account to respond in an emergency.',
+ style: context.themeText.smallParagraph?.copyWith(color: context.themeColors.textMuted),
+ ),
+ const Expanded(child: SizedBox()),
+ Row(
+ spacing: 36,
+ children: [
+ Expanded(
+ child: Button(
+ label: 'Back',
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ ),
+ Expanded(
+ child: Button(
+ isDisabled: isDisabled,
+ variant: ButtonVariant.neutral,
+ label: 'Next',
+ onPressed: () {
+ Navigator.push(context, MaterialPageRoute(builder: (context) => const HighSecuritySummaryWizard()));
+ },
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart
new file mode 100644
index 00000000..d7d9508b
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart
@@ -0,0 +1,157 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/components/gradient_text.dart';
+import 'package:resonance_network_wallet/features/components/scaffold_base.dart';
+import 'package:resonance_network_wallet/features/components/steps.dart';
+import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart';
+import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_confirmation_sheet.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+import 'package:resonance_network_wallet/providers/high_security_form_provider.dart';
+
+class HighSecuritySummaryWizard extends ConsumerStatefulWidget {
+ const HighSecuritySummaryWizard({super.key});
+
+ @override
+ ConsumerState createState() => _HighSecuritySummaryWizardState();
+}
+
+class _HighSecuritySummaryWizardState extends ConsumerState {
+ final HumanReadableChecksumService _humanReadableChecksumService = HumanReadableChecksumService();
+
+ @override
+ Widget build(BuildContext context) {
+ final formData = ref.read(highSecurityFormProvider);
+
+ final guardianChecksumFuture = _humanReadableChecksumService.getHumanReadableName(formData.guardianAddress);
+
+ return ScaffoldBase(
+ appBar: WalletAppBar.simpleWithBackButton(title: 'Summary'),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 36),
+ const Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SizedBox(
+ width: 204,
+ child: StepsIndicator(currentStep: 3, totalSteps: AppConstants.highSecurityStepsCount),
+ ),
+ ],
+ ),
+ const SizedBox(height: 32),
+ GradientText('SUMMARY', colors: context.themeColors.aquaBlue, style: context.themeText.largeTitle),
+ const SizedBox(height: 19),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 4,
+ children: [
+ Text('HIGH SECURITY ACCOUNT:', style: context.themeText.detail),
+ Text('Everyday Account', style: context.themeText.smallTitle),
+ Text(
+ 'Grain-Red-Flash-Hyper-Cloud',
+ style: context.themeText.smallParagraph?.copyWith(color: context.themeColors.checksumDarker),
+ ),
+ SizedBox(
+ width: 220,
+ child: Text(
+ '5FEUm MJ6w5 36upW fhFcK n61jN UniW3 norvT ULjwj MhbfN cs4N',
+ style: context.themeText.detail?.copyWith(color: Colors.white.useOpacity(0.6000000238418579)),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 19),
+ SummaryCard(
+ type: SummaryType.guardian,
+ checksumFuture: guardianChecksumFuture,
+ address: AddressFormattingService.splitIntoChunks(formData.guardianAddress).join(' '),
+ ),
+ const Expanded(child: SizedBox()),
+ Row(
+ spacing: 36,
+ children: [
+ Expanded(
+ child: Button(
+ label: 'Back',
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ ),
+ Expanded(
+ child: Button(
+ variant: ButtonVariant.neutral,
+ label: 'Next',
+ onPressed: () {
+ showHighSecurityConfirmationSheet(context);
+ },
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: context.themeSize.bottomButtonSpacing),
+ ],
+ ),
+ );
+ }
+}
+
+enum SummaryType { guardian, recovery }
+
+class SummaryCard extends StatelessWidget {
+ final SummaryType type;
+ final Future checksumFuture;
+ final String address;
+
+ const SummaryCard({super.key, required this.type, required this.checksumFuture, required this.address});
+
+ @override
+ Widget build(BuildContext context) {
+ final String label = type == SummaryType.guardian ? 'GUARDIAN ACCOUNT:' : 'RECOVERY ACCOUNT:';
+ final Color checksumColor = type == SummaryType.guardian
+ ? context.themeColors.yellow
+ : context.themeColors.buttonDanger;
+
+ return Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(10),
+ decoration: ShapeDecoration(
+ color: const Color(0x99313131),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 2,
+ children: [
+ Text(label, style: context.themeText.detail),
+ FutureBuilder(
+ future: checksumFuture,
+ builder: (context, snapshot) {
+ String text = 'Loading checksum...';
+
+ if (snapshot.error != null) {
+ text = 'Failed loading checksum: ${snapshot.error}';
+ } else if (snapshot.hasData) {
+ text = snapshot.data!;
+ }
+
+ return Text(text, style: context.themeText.smallParagraph?.copyWith(color: checksumColor));
+ },
+ ),
+ SizedBox(
+ width: 220,
+ child: Text(
+ address,
+ style: context.themeText.detail?.copyWith(color: Colors.white.useOpacity(0.6000000238418579)),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart b/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart
new file mode 100644
index 00000000..4b8c6bf8
--- /dev/null
+++ b/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart
@@ -0,0 +1,239 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:resonance_network_wallet/features/components/app_modal_bottom_sheet.dart';
+import 'package:resonance_network_wallet/features/components/button.dart';
+import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_size_theme.dart';
+import 'package:resonance_network_wallet/features/styles/app_text_theme.dart';
+
+class SafeguardWindowPickerSheet extends StatelessWidget {
+ final int safeguardTimeMonths;
+ final int safeguardTimeDays;
+ final int safeguardTimeHours;
+ final Function(int) setSafeguardTimeSeconds;
+
+ const SafeguardWindowPickerSheet({
+ super.key,
+ required this.safeguardTimeMonths,
+ required this.safeguardTimeDays,
+ required this.safeguardTimeHours,
+ required this.setSafeguardTimeSeconds,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ var selectedMonths = safeguardTimeMonths;
+ var selectedDays = safeguardTimeDays;
+ var selectedHours = safeguardTimeHours;
+
+ return Container(
+ height: MediaQuery.of(context).size.height * 0.75,
+ padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 60),
+ decoration: const BoxDecoration(
+ color: Colors.black,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
+ ),
+ child: Column(
+ children: [
+ // Header
+ Column(
+ children: [
+ SvgPicture.asset('assets/hourglass.svg', width: 29),
+ const SizedBox(height: 16),
+ Text(
+ 'Set Safeguard Window',
+ textAlign: TextAlign.center,
+ style: context.themeText.smallTitle?.copyWith(color: context.themeColors.checksum),
+ ),
+ const SizedBox(height: 4),
+ SizedBox(
+ width: context.themeSize.timePickerSubtitleWidth,
+ child: Text(
+ 'The Guardian can intercept a transaction during this period',
+ textAlign: TextAlign.center,
+ style: context.themeText.detail,
+ ),
+ ),
+ ],
+ ),
+
+ const SizedBox(height: 20),
+
+ // Time pickers
+ Expanded(
+ child: Row(
+ children: [
+ // Months
+ Expanded(
+ child: Column(
+ children: [
+ Text('Months', style: context.themeText.largeTag?.copyWith(color: context.themeColors.textMuted)),
+ const SizedBox(height: 8),
+ Expanded(
+ child: Row(
+ children: [
+ Expanded(
+ child: CupertinoPicker(
+ scrollController: FixedExtentScrollController(initialItem: selectedMonths),
+ itemExtent: 40,
+ onSelectedItemChanged: (index) => selectedMonths = index,
+ children: List.generate(
+ 13,
+ (index) => Center(
+ child: Text(
+ index.toString().padLeft(2, '0'),
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 28,
+ fontFamily: 'Fira Code',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ const Text(':', style: TextStyle(color: Colors.white, fontSize: 28)),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ // Days
+ Expanded(
+ child: Column(
+ children: [
+ Text('Days', style: context.themeText.largeTag?.copyWith(color: context.themeColors.textMuted)),
+ const SizedBox(height: 8),
+ Expanded(
+ child: Row(
+ children: [
+ Expanded(
+ child: CupertinoPicker(
+ scrollController: FixedExtentScrollController(initialItem: selectedDays),
+ itemExtent: 40,
+ onSelectedItemChanged: (index) => selectedDays = index,
+ children: List.generate(
+ 30,
+ (index) => Center(
+ child: Text(
+ index.toString().padLeft(2, '0'),
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 28,
+ fontFamily: 'Fira Code',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ const Text(':', style: TextStyle(color: Colors.white, fontSize: 28)),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ // Hours
+ Expanded(
+ child: Column(
+ children: [
+ Text('Hours', style: context.themeText.largeTag?.copyWith(color: context.themeColors.textMuted)),
+ const SizedBox(height: 8),
+ Expanded(
+ child: CupertinoPicker(
+ scrollController: FixedExtentScrollController(initialItem: selectedHours),
+ itemExtent: 40,
+ onSelectedItemChanged: (index) => selectedHours = index,
+ children: List.generate(
+ 24,
+ (index) => Center(
+ child: Text(
+ index.toString().padLeft(2, '0'),
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 28,
+ fontFamily: 'Fira Code',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+
+ const SizedBox(height: 20),
+
+ // Action buttons
+ Row(
+ children: [
+ Expanded(
+ child: Button(
+ variant: ButtonVariant.neutral,
+ label: 'Cancel',
+ textStyle: context.themeText.paragraph?.copyWith(
+ color: context.themeColors.textSecondary,
+ fontWeight: FontWeight.w600,
+ ),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Button(
+ variant: ButtonVariant.success,
+ label: 'Set',
+ textStyle: context.themeText.paragraph?.copyWith(
+ color: context.themeColors.textSecondary,
+ fontWeight: FontWeight.w600,
+ ),
+ onPressed: () {
+ final int secondsInAMonth = 86400 * 30; // 86400 seconds/day * 30 days/month
+ final newTimeSeconds =
+ (selectedMonths * secondsInAMonth) + (selectedDays * 86400) + (selectedHours * 3600);
+
+ setSafeguardTimeSeconds(newTimeSeconds);
+ Navigator.pop(context);
+ },
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 35),
+ ],
+ ),
+ );
+ }
+}
+
+void showSafeguardWindowPickerSheet(
+ BuildContext context, {
+ required int safeguardTimeMonths,
+ required int safeguardTimeDays,
+ required int safeguardTimeHours,
+
+ required Function(int) setSafeguardTimeSeconds,
+}) {
+ showAppModalBottomSheet(
+ context: context,
+ builder: (context) => SafeguardWindowPickerSheet(
+ safeguardTimeMonths: safeguardTimeMonths,
+ safeguardTimeDays: safeguardTimeDays,
+ safeguardTimeHours: safeguardTimeHours,
+
+ setSafeguardTimeSeconds: setSafeguardTimeSeconds,
+ ),
+ );
+}
diff --git a/mobile-app/lib/features/styles/app_colors_theme.dart b/mobile-app/lib/features/styles/app_colors_theme.dart
index 637f9dbb..f5dab625 100644
--- a/mobile-app/lib/features/styles/app_colors_theme.dart
+++ b/mobile-app/lib/features/styles/app_colors_theme.dart
@@ -8,6 +8,7 @@ class AppColorsTheme extends ThemeExtension {
final Color secondary;
// What we use
+ final List aquaBlue;
final Color purple;
final Color pink;
final Color yellow;
@@ -43,6 +44,7 @@ class AppColorsTheme extends ThemeExtension {
required this.primary,
required this.secondary,
+ required this.aquaBlue,
required this.purple,
required this.pink,
required this.yellow,
@@ -80,6 +82,7 @@ class AppColorsTheme extends ThemeExtension {
primary: const Color(0xFF6B46C1),
secondary: const Color(0xFF9F7AEA),
+ aquaBlue: const [Color(0xFF16CECE), Color(0xFF0000FF)],
purple: const Color(0xFFB259F2),
pink: const Color(0xFFED4CCE),
yellow: const Color(0xFFFFE91F),
@@ -117,6 +120,7 @@ class AppColorsTheme extends ThemeExtension {
primary: const Color(0xFF6B46C1),
secondary: const Color(0xFF9F7AEA),
+ aquaBlue: const [Color(0xFF16CECE), Color(0xFF0000FF)],
purple: const Color(0xFFB259F2),
pink: const Color(0xFFED4CCE),
yellow: const Color(0xFFFFE91F),
@@ -153,6 +157,7 @@ class AppColorsTheme extends ThemeExtension {
AppColorsTheme copyWith({
Color? primary,
Color? secondary,
+ List? aquaBlue,
Color? purple,
Color? pink,
Color? yellow,
@@ -188,6 +193,7 @@ class AppColorsTheme extends ThemeExtension {
return AppColorsTheme(
primary: primary ?? this.primary,
secondary: secondary ?? this.secondary,
+ aquaBlue: aquaBlue ?? this.aquaBlue,
purple: purple ?? this.purple,
pink: pink ?? this.pink,
yellow: yellow ?? this.yellow,
@@ -227,6 +233,7 @@ class AppColorsTheme extends ThemeExtension {
return AppColorsTheme(
primary: Color.lerp(primary, other.primary, t) ?? primary,
secondary: Color.lerp(secondary, other.secondary, t) ?? secondary,
+ aquaBlue: other.aquaBlue,
purple: Color.lerp(purple, other.purple, t) ?? purple,
pink: Color.lerp(pink, other.pink, t) ?? pink,
yellow: Color.lerp(yellow, other.yellow, t) ?? yellow,
diff --git a/mobile-app/lib/features/styles/app_size_theme.dart b/mobile-app/lib/features/styles/app_size_theme.dart
index bbabb172..9da58c2e 100644
--- a/mobile-app/lib/features/styles/app_size_theme.dart
+++ b/mobile-app/lib/features/styles/app_size_theme.dart
@@ -27,6 +27,8 @@ class AppSizeTheme extends ThemeExtension {
final double pasteIconSize;
final double timePickerSubtitleWidth;
final double bottomButtonSpacing;
+ final double buttonsHorizontalSpacing;
+ final double infoSheetTitleIcon;
const AppSizeTheme({
required this.logoHeight,
@@ -54,6 +56,8 @@ class AppSizeTheme extends ThemeExtension {
required this.pasteIconSize,
required this.timePickerSubtitleWidth,
required this.bottomButtonSpacing,
+ required this.buttonsHorizontalSpacing,
+ required this.infoSheetTitleIcon,
});
const AppSizeTheme.defaultTheme()
@@ -83,6 +87,8 @@ class AppSizeTheme extends ThemeExtension {
pasteIconSize: 18.0,
timePickerSubtitleWidth: 249,
bottomButtonSpacing: 16,
+ buttonsHorizontalSpacing: 28,
+ infoSheetTitleIcon: 25,
);
const AppSizeTheme.iPad()
@@ -112,6 +118,8 @@ class AppSizeTheme extends ThemeExtension {
pasteIconSize: 24.0,
timePickerSubtitleWidth: 400,
bottomButtonSpacing: 16,
+ buttonsHorizontalSpacing: 28,
+ infoSheetTitleIcon: 28,
);
@override
@@ -170,6 +178,8 @@ class AppSizeTheme extends ThemeExtension {
pasteIconSize: pasteIconSize ?? this.pasteIconSize,
timePickerSubtitleWidth: timePickerSubtitleWidth ?? this.timePickerSubtitleWidth,
bottomButtonSpacing: bottomButtonSpacing ?? this.bottomButtonSpacing,
+ buttonsHorizontalSpacing: buttonsHorizontalSpacing ?? this.buttonsHorizontalSpacing,
+ infoSheetTitleIcon: infoSheetTitleIcon ?? this.infoSheetTitleIcon,
);
}
@@ -206,6 +216,9 @@ class AppSizeTheme extends ThemeExtension {
pasteIconSize: pasteIconSize + (other.pasteIconSize - pasteIconSize) * t,
timePickerSubtitleWidth: timePickerSubtitleWidth + (other.timePickerSubtitleWidth - timePickerSubtitleWidth) * t,
bottomButtonSpacing: bottomButtonSpacing + (other.bottomButtonSpacing - bottomButtonSpacing) * t,
+ buttonsHorizontalSpacing:
+ buttonsHorizontalSpacing + (other.buttonsHorizontalSpacing - buttonsHorizontalSpacing) * t,
+ infoSheetTitleIcon: infoSheetTitleIcon + (other.infoSheetTitleIcon - infoSheetTitleIcon) * t,
);
}
}
diff --git a/mobile-app/lib/providers/entrusted_account_provider.dart b/mobile-app/lib/providers/entrusted_account_provider.dart
new file mode 100644
index 00000000..cf9f4651
--- /dev/null
+++ b/mobile-app/lib/providers/entrusted_account_provider.dart
@@ -0,0 +1,31 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+
+final entrustedAccountsProvider = FutureProvider.family, Account>((ref, account) async {
+ // TODO: Implement actual fetching of entrusted accounts from SDK/API
+ // For now we simulate the delay and return empty list or dummy data
+
+ await Future.delayed(const Duration(milliseconds: 500));
+
+ // Dummy data logic for demonstration/development
+ // If you want to see the UI, you can uncomment this or use a specific account ID
+ if (account.name.startsWith('G')) {
+ // arbitrary condition for testing
+ return [
+ const Account(
+ walletIndex: 0,
+ index: 0,
+ name: 'Entrusted Account 1',
+ accountId: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
+ ),
+ const Account(
+ walletIndex: 0,
+ index: 0,
+ name: 'Zander Sky',
+ accountId: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
+ ),
+ ];
+ }
+
+ return [];
+});
diff --git a/mobile-app/lib/providers/high_security_form_provider.dart b/mobile-app/lib/providers/high_security_form_provider.dart
new file mode 100644
index 00000000..c0b44207
--- /dev/null
+++ b/mobile-app/lib/providers/high_security_form_provider.dart
@@ -0,0 +1,22 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:quantus_sdk/quantus_sdk.dart';
+
+class HighSecurityFormNotifier extends StateNotifier {
+ HighSecurityFormNotifier() : super(const HighSecurityData());
+ void updateGuardianAddress(String address) {
+ state = state.copyWith(guardianAddress: address);
+ }
+
+ void updateSafeguardWindow(int window) {
+ state = state.copyWith(safeguardWindow: window);
+ }
+
+ void resetState() {
+ state = const HighSecurityData();
+ }
+}
+
+// Provider
+final highSecurityFormProvider = StateNotifierProvider((ref) {
+ return HighSecurityFormNotifier();
+});
diff --git a/mobile-app/lib/utils/feature_flags.dart b/mobile-app/lib/utils/feature_flags.dart
index 0f93d5f5..07623f6d 100644
--- a/mobile-app/lib/utils/feature_flags.dart
+++ b/mobile-app/lib/utils/feature_flags.dart
@@ -1,5 +1,6 @@
// Simple feature flags for things we want in the code but not yet in the production app
class FeatureFlags {
static const bool enableTestButtons = false; // Only show in debug mode
- static const bool showKeystoneHardwareWallet = false; // turn keystone hw wallet on and off
+ static const bool enableKeystoneHardwareWallet = false; // turn keystone hw wallet on and off
+ static const bool enableHighSecurity = false; // turn keystone hw wallet on and off
}
diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml
index a8992a56..14ebec12 100644
--- a/mobile-app/pubspec.yaml
+++ b/mobile-app/pubspec.yaml
@@ -46,7 +46,7 @@ dependencies:
share_plus: ^12.0.1
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
- telemetrydecksdk: ^2.5.0
+ telemetrydecksdk: ^3.0.0
async: ^2.13.0
app_links: ^6.4.1
flutter_dotenv: ^5.1.0
@@ -94,6 +94,9 @@ flutter:
- assets/qq-logo.png
- assets/navbar/qcat_navbar_icon.png
- assets/notification/notification_top_icon.png
+ - assets/high_security/security_icon_big.svg
+ - assets/high_security/step_indicator_active_icon.svg
+ - assets/high_security/step_indicator_icon.svg
fonts:
- family: Fira Code
diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart
index 132f6652..70c67a1f 100644
--- a/quantus_sdk/lib/quantus_sdk.dart
+++ b/quantus_sdk/lib/quantus_sdk.dart
@@ -15,6 +15,7 @@ export 'src/extensions/keypair_extensions.dart';
export 'src/extensions/string_extensions.dart';
// UI-related exports
export 'src/models/account.dart';
+export 'src/models/high_security_data.dart';
export 'src/models/account_stats.dart';
export 'src/models/account_associations.dart';
export 'src/models/event_type.dart';
@@ -43,6 +44,7 @@ export 'src/services/chain_history_service.dart';
export 'src/services/connectivity_service.dart';
export 'src/services/datetime_formatting_service.dart';
export 'src/services/hd_wallet_service.dart';
+export 'src/services/high_security_service.dart';
export 'src/services/human_readable_checksum_service.dart';
export 'src/services/migration_service.dart';
export 'src/services/network/redundant_endpoint.dart';
diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart
index 3119b43a..a6014055 100644
--- a/quantus_sdk/lib/src/constants/app_constants.dart
+++ b/quantus_sdk/lib/src/constants/app_constants.dart
@@ -57,4 +57,7 @@ class AppConstants {
// This starts the hardware wallet flow using a soft wallet - quite useful for debugging
// hardware wallet flow without using a hardware wallet.
static const debugHardwareWallet = false;
+
+ static const String accountSettingsRouteName = 'account-settings';
+ static const int highSecurityStepsCount = 3;
}
diff --git a/quantus_sdk/lib/src/models/high_security_data.dart b/quantus_sdk/lib/src/models/high_security_data.dart
new file mode 100644
index 00000000..16020882
--- /dev/null
+++ b/quantus_sdk/lib/src/models/high_security_data.dart
@@ -0,0 +1,16 @@
+class HighSecurityData {
+ final String guardianAddress;
+ final int safeguardWindow;
+
+ const HighSecurityData({
+ this.guardianAddress = '',
+ this.safeguardWindow = 10 * 60 * 60, // 10 hours in seconds
+ });
+
+ HighSecurityData copyWith({String? guardianAddress, int? safeguardWindow}) {
+ return HighSecurityData(
+ guardianAddress: guardianAddress ?? this.guardianAddress,
+ safeguardWindow: safeguardWindow ?? this.safeguardWindow,
+ );
+ }
+}
diff --git a/quantus_sdk/lib/src/services/datetime_formatting_service.dart b/quantus_sdk/lib/src/services/datetime_formatting_service.dart
index 82b61687..24c44168 100644
--- a/quantus_sdk/lib/src/services/datetime_formatting_service.dart
+++ b/quantus_sdk/lib/src/services/datetime_formatting_service.dart
@@ -125,4 +125,17 @@ class DatetimeFormattingService {
static String _pluralize(int count) {
return count == 1 ? '' : 's';
}
+
+ static String formatSafeguardTime(int months, int days, int hours) {
+ if (months > 0) {
+ return '$months month${months > 1 ? 's' : ''}, '
+ '$days day${days != 1 ? 's' : ''}, \n'
+ '$hours hr${hours != 1 ? 's' : ''}';
+ } else if (days > 0) {
+ return '$days day${days != 1 ? 's' : ''}, '
+ '$hours hr${hours != 1 ? 's' : ''}';
+ } else {
+ return '$hours hr${hours != 1 ? 's' : ''}';
+ }
+ }
}
diff --git a/quantus_sdk/lib/src/services/high_security_service.dart b/quantus_sdk/lib/src/services/high_security_service.dart
new file mode 100644
index 00000000..c8caa1ea
--- /dev/null
+++ b/quantus_sdk/lib/src/services/high_security_service.dart
@@ -0,0 +1,46 @@
+import 'dart:async';
+
+import 'package:quantus_sdk/quantus_sdk.dart';
+
+class HighSecurityService {
+ static final HighSecurityService _instance = HighSecurityService._internal();
+ factory HighSecurityService() => _instance;
+ HighSecurityService._internal();
+
+ // ignore: unused_field
+ final SubstrateService _substrateService = SubstrateService();
+
+ Future setupHighSecurityAccount(Account account, HighSecurityData formData) async {
+ try {
+ await Future.delayed(const Duration(seconds: 2));
+ // Submit the extrinsic and return its result
+ // return await _substrateService.submitExtrinsic(account, runtimeCall);
+ } catch (e, stackTrace) {
+ print('Failed to setup: $e');
+ print('Failed to setup: $stackTrace');
+ throw Exception('Failed to setup: $e');
+ }
+ }
+
+ // TODO replace with actual fee calculation
+ Future getHighSecuritySetupFee(Account account, HighSecurityData formData) async {
+ try {
+ await Future.delayed(const Duration(seconds: 2));
+
+ // Mock fetch
+ return ExtrinsicFeeData(
+ fee: BigInt.from(1000000000000000000), // 1.0
+ blockHash: '0x0',
+ blockNumber: 0,
+ );
+ } catch (e, stackTrace) {
+ print('Failed to get setup fee: $e');
+ print('Failed to get setup fee: $stackTrace');
+ throw Exception('Failed to get setup fee: $e');
+ }
+ }
+
+ void getHighSecuritySetupCall(HighSecurityData formData) {
+ throw Exception('No Implementation');
+ }
+}