diff --git a/package-lock.json b/package-lock.json index ead1426..9e4cc83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", + "react-native-view-shot": "^3.8.0", "react-native-web": "~0.19.13" }, "devDependencies": { @@ -79,6 +80,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -482,7 +484,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" @@ -499,7 +500,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -515,7 +515,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -531,7 +530,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", @@ -549,7 +547,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/traverse": "^7.28.6" @@ -650,7 +647,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" }, @@ -771,7 +767,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -958,7 +953,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1024,7 +1018,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1071,7 +1064,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" @@ -1140,7 +1132,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1157,7 +1148,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1173,7 +1163,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1190,7 +1179,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1206,7 +1194,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5" @@ -1223,7 +1210,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1303,7 +1289,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1349,7 +1334,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1365,7 +1349,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1398,7 +1381,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", @@ -1417,7 +1399,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1450,7 +1431,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1515,7 +1495,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" @@ -1611,7 +1590,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1737,7 +1715,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1754,7 +1731,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1851,7 +1827,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1886,7 +1861,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1902,7 +1876,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1935,7 +1908,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -2054,7 +2026,6 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -2672,6 +2643,7 @@ "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-4.0.1.tgz", "integrity": "sha512-CRpbLvdJ1T42S+lrYa1iZp1KfDeBp4oeZOK3hdpiS5n0vR0nhD6sC1gGF0sTboCTp64tLteikz5Y3j53dvgOIw==", "license": "MIT", + "peer": true, "peerDependencies": { "react-native": "*" } @@ -3716,6 +3688,7 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.18.tgz", "integrity": "sha512-mIT9MiL/vMm4eirLcmw2h6h/Nm5FICtnYSdohq4vTLA2FF/6PNhByM7s8ffqoVfE5L0uAa6Xda1B7oddolUiGg==", "license": "MIT", + "peer": true, "dependencies": { "@react-navigation/core": "^6.4.17", "escape-string-regexp": "^4.0.0", @@ -3897,6 +3870,7 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4370,6 +4344,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4499,6 +4482,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5155,6 +5139,15 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5514,7 +5507,6 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5639,6 +5631,7 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.48.tgz", "integrity": "sha512-/HR/vuo57KGEWlvF3GWaquwEAjXuA5hrOCsaLcZ3pMSA8mQ27qKd1jva4GWzpxXYedlzs/7LLP1XpZo6hXTsog==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.22.27", @@ -6438,6 +6431,19 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -9153,6 +9159,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -9236,6 +9243,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.76.9.tgz", "integrity": "sha512-+LRwecWmTDco7OweGsrECIqJu0iyrREd6CTCgC/uLLYipiHvk+MH9nd6drFtCw/6Blz6eoKTcH9YTTJusNtrWg==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native/assets-registry": "0.76.9", @@ -9337,6 +9345,7 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz", "integrity": "sha512-ukk5PxcF4p3yu6qMZcmeiZgowhb5AsKRnil54YFUUAXVIS7PJcMHGGC+q44fCiBg44/1AJk5njGMez1m9H0BVQ==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -9347,6 +9356,7 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.4.0.tgz", "integrity": "sha512-c7zc7Zwjty6/pGyuuvh9gK3YBYqHPOxrhXfG1lF4gHlojQSmIx2piNbNaV+Uykj+RDTmFXK0e/hA+fucw/Qozg==", "license": "MIT", + "peer": true, "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" @@ -9356,6 +9366,19 @@ "react-native": "*" } }, + "node_modules/react-native-view-shot": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-3.8.0.tgz", + "integrity": "sha512-4cU8SOhMn3YQIrskh+5Q8VvVRxQOu8/s1M9NAL4z5BY1Rm0HXMWkQJ4N0XsZ42+Yca+y86ISF3LC5qdLPvPuiA==", + "license": "MIT", + "dependencies": { + "html2canvas": "^1.4.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-web": { "version": "0.19.13", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.tgz", @@ -10605,6 +10628,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10892,6 +10924,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index 3b220f6..e8c548d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "react-dom": "18.3.1", "react-native": "^0.76.9", "react-native-gesture-handler": "~2.20.2", + "react-native-view-shot": "^3.8.0", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", diff --git a/src/screens/SentinelScreen.tsx b/src/screens/SentinelScreen.tsx index 4d2be69..795d3c7 100644 --- a/src/screens/SentinelScreen.tsx +++ b/src/screens/SentinelScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { View, Text, @@ -8,13 +8,47 @@ import { SafeAreaView, Animated, Modal, + TextInput, + KeyboardAvoidingView, + Platform, + Share, + Alert, + Linking, } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { Ionicons, Feather, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons'; +import { captureRef } from 'react-native-view-shot'; +import AsyncStorage from '@react-native-async-storage/async-storage'; import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors'; import { SystemStatus, KillSwitchLog } from '../types'; import VaultScreen from './VaultScreen'; +const MNEMONIC_WORDS = [ + 'anchor', 'harbor', 'compass', 'lighthouse', 'current', 'ocean', 'tide', 'voyage', + 'keel', 'stern', 'bow', 'mast', 'sail', 'port', 'starboard', 'reef', + 'signal', 'beacon', 'chart', 'helm', 'gale', 'calm', 'cove', 'isle', + 'horizon', 'sextant', 'sound', 'drift', 'wake', 'mariner', 'pilot', 'fathom', + 'buoy', 'lantern', 'harpoon', 'lagoon', 'bay', 'strait', 'riptide', 'foam', + 'coral', 'pearl', 'trident', 'ebb', 'flow', 'vault', 'cipher', 'shroud', + 'salt', 'wave', 'grotto', 'lagoon', 'storm', 'north', 'south', 'east', + 'west', 'ember', 'cabin', 'signal', 'ledger', 'torch', 'sanctum', 'oath', +]; + +const generateMnemonic = (wordCount = 12) => { + const words: string[] = []; + for (let i = 0; i < wordCount; i += 1) { + const index = Math.floor(Math.random() * MNEMONIC_WORDS.length); + words.push(MNEMONIC_WORDS[index]); + } + return words; +}; + +const splitMnemonic = (words: string[]) => [ + words.slice(0, 4), + words.slice(4, 8), + words.slice(8, 12), +]; + // Status configuration with nautical theme const statusConfig: Record([]); + const [mnemonicParts, setMnemonicParts] = useState([]); + const [showEmailForm, setShowEmailForm] = useState(false); + const [emailAddress, setEmailAddress] = useState(''); + const [isCapturing, setIsCapturing] = useState(false); + const mnemonicRef = useRef(null); useEffect(() => { // Pulse animation @@ -123,6 +164,67 @@ export default function SentinelScreen() { ).start(); }, []); + const openVaultWithMnemonic = () => { + const words = generateMnemonic(); + const parts = splitMnemonic(words); + setMnemonicWords(words); + setMnemonicParts(parts); + setShowMnemonic(true); + setShowVault(false); + setShowEmailForm(false); + setEmailAddress(''); + AsyncStorage.setItem('sentinel_mnemonic_part_local', parts[0].join(' ')).catch(() => { + // Best-effort local store; UI remains available + }); + }; + + const handleScreenshot = async () => { + try { + setIsCapturing(true); + const uri = await captureRef(mnemonicRef, { + format: 'png', + quality: 1, + result: 'tmpfile', + }); + await Share.share({ + url: uri, + message: 'Sentinel key backup', + }); + setShowMnemonic(false); + setShowVault(true); + } catch (error) { + Alert.alert('Screenshot failed', 'Please try again or use email backup.'); + } finally { + setIsCapturing(false); + } + }; + + const handleEmailBackup = () => { + setShowEmailForm(true); + }; + + const handleSendEmail = async () => { + const trimmed = emailAddress.trim(); + if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) { + Alert.alert('Invalid email', 'Please enter a valid email address.'); + return; + } + + const subject = encodeURIComponent('Sentinel Vault Recovery Key'); + const body = encodeURIComponent(`Your 12-word mnemonic:\n${mnemonicWords.join(' ')}`); + const mailtoUrl = `mailto:${trimmed}?subject=${subject}&body=${body}`; + + try { + await Linking.openURL(mailtoUrl); + setShowMnemonic(false); + setShowEmailForm(false); + setEmailAddress(''); + setShowVault(true); + } catch (error) { + Alert.alert('Email failed', 'Unable to open email client.'); + } + }; + const handleHeartbeat = () => { // Animate pulse Animated.sequence([ @@ -286,7 +388,7 @@ export default function SentinelScreen() { setShowVault(true)} + onPress={openVaultWithMnemonic} activeOpacity={0.8} > Open @@ -354,6 +456,100 @@ export default function SentinelScreen() { + + {/* Mnemonic Modal */} + setShowMnemonic(false)} + > + + + setShowMnemonic(false)} + activeOpacity={0.85} + > + + + + + 12-Word Mnemonic + + + Your mnemonic is split into 3 parts (4/4/4). Part 1 is stored locally. + + + + {mnemonicWords.join(' ')} + + + + + PART 1 • LOCAL + {mnemonicParts[0]?.join(' ')} + Stored on this device + + + PART 2 • CLOUD NODE + {mnemonicParts[1]?.join(' ')} + To be synced + + + PART 3 • HEIR + {mnemonicParts[2]?.join(' ')} + To be assigned + + + + + {isCapturing ? 'CAPTURING...' : 'PHYSICAL BACKUP (SCREENSHOT)'} + + + + EMAIL BACKUP + + {showEmailForm ? ( + + + + SEND EMAIL + + + ) : null} + + + ); } @@ -614,4 +810,148 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, + mnemonicOverlay: { + flex: 1, + backgroundColor: 'rgba(11, 20, 24, 0.72)', + justifyContent: 'center', + padding: spacing.lg, + }, + mnemonicCard: { + borderRadius: borderRadius.xl, + padding: spacing.lg, + borderWidth: 1, + borderColor: colors.sentinel.cardBorder, + ...shadows.glow, + }, + mnemonicHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.sm, + marginBottom: spacing.sm, + }, + mnemonicClose: { + position: 'absolute', + top: spacing.sm, + right: spacing.sm, + width: 32, + height: 32, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(26, 58, 74, 0.35)', + }, + mnemonicTitle: { + fontSize: typography.fontSize.lg, + fontWeight: '700', + color: colors.sentinel.text, + letterSpacing: typography.letterSpacing.wide, + }, + mnemonicSubtitle: { + fontSize: typography.fontSize.sm, + color: colors.sentinel.textSecondary, + marginBottom: spacing.md, + }, + mnemonicBlock: { + backgroundColor: colors.sentinel.cardBackground, + borderRadius: borderRadius.lg, + paddingVertical: spacing.md, + paddingHorizontal: spacing.md, + borderWidth: 1, + borderColor: colors.sentinel.cardBorder, + marginBottom: spacing.lg, + }, + partGrid: { + gap: spacing.sm, + marginBottom: spacing.lg, + }, + partCard: { + backgroundColor: colors.sentinel.cardBackground, + borderRadius: borderRadius.lg, + paddingVertical: spacing.sm, + paddingHorizontal: spacing.md, + borderWidth: 1, + borderColor: colors.sentinel.cardBorder, + }, + partCardStored: { + borderColor: colors.sentinel.primary, + }, + partLabel: { + fontSize: typography.fontSize.xs, + color: colors.sentinel.textSecondary, + letterSpacing: typography.letterSpacing.wide, + marginBottom: 4, + fontWeight: '600', + }, + partValue: { + fontSize: typography.fontSize.md, + color: colors.sentinel.text, + fontFamily: typography.fontFamily.mono, + fontWeight: '700', + marginBottom: 2, + }, + partHint: { + fontSize: typography.fontSize.xs, + color: colors.sentinel.textSecondary, + }, + mnemonicBlockText: { + fontSize: typography.fontSize.sm, + color: colors.sentinel.text, + fontFamily: typography.fontFamily.mono, + fontWeight: '600', + lineHeight: 22, + textAlign: 'center', + }, + mnemonicPrimaryButton: { + backgroundColor: colors.sentinel.primary, + paddingVertical: spacing.sm, + borderRadius: borderRadius.full, + alignItems: 'center', + marginBottom: spacing.sm, + }, + mnemonicButtonDisabled: { + opacity: 0.6, + }, + mnemonicPrimaryText: { + color: colors.nautical.cream, + fontWeight: '700', + letterSpacing: typography.letterSpacing.wide, + }, + mnemonicSecondaryButton: { + backgroundColor: 'transparent', + paddingVertical: spacing.sm, + borderRadius: borderRadius.full, + alignItems: 'center', + borderWidth: 1, + borderColor: colors.sentinel.cardBorder, + }, + mnemonicSecondaryText: { + color: colors.sentinel.text, + fontWeight: '700', + letterSpacing: typography.letterSpacing.wide, + }, + emailForm: { + marginTop: spacing.sm, + }, + emailInput: { + height: 44, + borderRadius: borderRadius.full, + borderWidth: 1, + borderColor: colors.sentinel.cardBorder, + paddingHorizontal: spacing.md, + color: colors.sentinel.text, + fontSize: typography.fontSize.sm, + backgroundColor: 'rgba(255, 255, 255, 0.02)', + marginBottom: spacing.sm, + }, + emailSendButton: { + backgroundColor: colors.nautical.teal, + paddingVertical: spacing.sm, + borderRadius: borderRadius.full, + alignItems: 'center', + }, + emailSendText: { + color: colors.nautical.cream, + fontWeight: '700', + letterSpacing: typography.letterSpacing.wide, + }, });