diff --git a/.metadata b/.metadata index 90eabcff..95ec97b7 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819" + revision: "edada7c56edf4a183c1735310e123c7f923584f1" channel: "stable" project_type: app @@ -13,8 +13,8 @@ project_type: app migration: platforms: - platform: root - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 - platform: android create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 @@ -31,8 +31,8 @@ migration: create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: windows - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 # User provided section diff --git a/apipod/apipod_server/pubspec.lock b/apipod/apipod_server/pubspec.lock index 8ff09351..fa78c101 100644 --- a/apipod/apipod_server/pubspec.lock +++ b/apipod/apipod_server/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a url: "https://pub.dev" source: hosted - version: "80.0.0" + version: "88.0.0" adaptive_number: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "8.1.1" args: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: asn1lib - sha256: e02d018628c870ef2d7f03e33f9ad179d89ff6ec52ca6c56bcb80bcef979867f + sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" url: "https://pub.dev" source: hosted - version: "1.6.2" + version: "1.6.5" async: dependency: transitive description: @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -133,10 +141,10 @@ packages: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.15.0" crypto: dependency: transitive description: @@ -237,10 +245,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -261,10 +269,10 @@ packages: dependency: transitive description: name: google_identity_services_web - sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9" + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" url: "https://pub.dev" source: hosted - version: "0.3.3" + version: "0.3.3+1" googleapis: dependency: transitive description: @@ -301,10 +309,10 @@ packages: dependency: "direct main" description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" http_multi_server: dependency: transitive description: @@ -373,10 +381,10 @@ packages: dependency: transitive description: name: logger - sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 + sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.1" logging: dependency: transitive description: @@ -397,10 +405,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -421,10 +429,10 @@ packages: dependency: "direct main" description: name: ndk - sha256: "2f64d055e2a656ab523e3d804280e81155a14c029d16d42b804de41ac7f815c2" + sha256: c084ec0d4f630a99b4a80fe092fb4f339db36c6f53c180431a57a69ea3b14188 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.4.1" node_preamble: dependency: transitive description: @@ -485,10 +493,10 @@ packages: dependency: transitive description: name: postgres - sha256: "7a7f9805d33e41cb14fa22535959f3af51843a792015cdbd8f2fa78bcd1b501b" + sha256: "9aaa6f4872956adef653535a4e2133b167465c1a68c22b9bd0744ef1244e9393" url: "https://pub.dev" source: hosted - version: "3.5.4" + version: "3.5.6" pub_semver: dependency: transitive description: @@ -693,10 +701,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" system_resources: dependency: transitive description: @@ -717,26 +725,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.25.15" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.8" + version: "0.6.12" typed_data: dependency: transitive description: @@ -749,10 +757,10 @@ packages: dependency: transitive description: name: unorm_dart - sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b" + sha256: "8e3870a1caa60bde8352f9597dd3535d8068613269444f8e35ea8925ec84c1f5" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.1+1" uuid: dependency: transitive description: @@ -765,18 +773,18 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" web: dependency: transitive description: @@ -789,10 +797,10 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: "direct main" description: @@ -842,4 +850,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0 <4.0.0" diff --git a/assets/animations/correct.json b/assets/animations/correct.json new file mode 100644 index 00000000..742fe7e6 --- /dev/null +++ b/assets/animations/correct.json @@ -0,0 +1 @@ +{"v":"5.6.7","fr":60,"ip":0,"op":180,"w":1200,"h":1200,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"check","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[575.25,603.465,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":114,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":130,"s":[105,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":136,"s":[95,95,100]},{"t":142,"s":[100,100,100]}],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Excite - Skew - Transform","np":8,"mn":"Pseudo/BNCA2506f0b33","ix":1,"en":1,"ef":[{"ty":7,"nm":"Enable","mn":"Pseudo/BNCA2506f0b33-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{"ty":6,"nm":"Properties","mn":"Pseudo/BNCA2506f0b33-0002","ix":2,"v":0},{"ty":0,"nm":"Overshoot","mn":"Pseudo/BNCA2506f0b33-0003","ix":3,"v":{"a":0,"k":10,"ix":3,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Bounce","mn":"Pseudo/BNCA2506f0b33-0004","ix":4,"v":{"a":0,"k":15,"ix":4,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Friction","mn":"Pseudo/BNCA2506f0b33-0005","ix":5,"v":{"a":0,"k":40,"ix":5,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":6,"nm":"","mn":"Pseudo/BNCA2506f0b33-0006","ix":6,"v":0}]},{"ty":5,"nm":"Excite - Scale - Transform","np":8,"mn":"Pseudo/BNCA2506f0b33","ix":2,"en":1,"ef":[{"ty":7,"nm":"Enable","mn":"Pseudo/BNCA2506f0b33-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{"ty":6,"nm":"Properties","mn":"Pseudo/BNCA2506f0b33-0002","ix":2,"v":0},{"ty":0,"nm":"Overshoot","mn":"Pseudo/BNCA2506f0b33-0003","ix":3,"v":{"a":0,"k":10,"ix":3,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Bounce","mn":"Pseudo/BNCA2506f0b33-0004","ix":4,"v":{"a":0,"k":15,"ix":4,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Friction","mn":"Pseudo/BNCA2506f0b33-0005","ix":5,"v":{"a":0,"k":40,"ix":5,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":6,"nm":"","mn":"Pseudo/BNCA2506f0b33-0006","ix":6,"v":0}]},{"ty":5,"nm":"Excite - Position - Transform","np":8,"mn":"Pseudo/BNCA2506f0b33","ix":3,"en":1,"ef":[{"ty":7,"nm":"Enable","mn":"Pseudo/BNCA2506f0b33-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{"ty":6,"nm":"Properties","mn":"Pseudo/BNCA2506f0b33-0002","ix":2,"v":0},{"ty":0,"nm":"Overshoot","mn":"Pseudo/BNCA2506f0b33-0003","ix":3,"v":{"a":0,"k":10,"ix":3,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Bounce","mn":"Pseudo/BNCA2506f0b33-0004","ix":4,"v":{"a":0,"k":15,"ix":4,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Friction","mn":"Pseudo/BNCA2506f0b33-0005","ix":5,"v":{"a":0,"k":40,"ix":5,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":6,"nm":"","mn":"Pseudo/BNCA2506f0b33-0006","ix":6,"v":0}]},{"ty":5,"nm":"Excite - Anchor Point - Transform","np":8,"mn":"Pseudo/BNCA2506f0b33","ix":4,"en":1,"ef":[{"ty":7,"nm":"Enable","mn":"Pseudo/BNCA2506f0b33-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{"ty":6,"nm":"Properties","mn":"Pseudo/BNCA2506f0b33-0002","ix":2,"v":0},{"ty":0,"nm":"Overshoot","mn":"Pseudo/BNCA2506f0b33-0003","ix":3,"v":{"a":0,"k":10,"ix":3,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Bounce","mn":"Pseudo/BNCA2506f0b33-0004","ix":4,"v":{"a":0,"k":15,"ix":4,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":0,"nm":"Friction","mn":"Pseudo/BNCA2506f0b33-0005","ix":5,"v":{"a":0,"k":40,"ix":5,"x":"var $bm_rt;\n$bm_rt = clamp(value, 0, 100);"}},{"ty":6,"nm":"","mn":"Pseudo/BNCA2506f0b33-0006","ix":6,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[214,-137],[-68,130],[-164,34]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":56,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2,"x":"var $bm_rt;\nvar enable, amp, freq, decay, n, t, v;\ntry {\n $bm_rt = enable = effect('Excite - Position - Transform')('Pseudo/BNCA2506f0b33-0001');\n if (enable == 0) {\n $bm_rt = value;\n } else {\n amp = $bm_div(effect('Excite - Position - Transform')('Pseudo/BNCA2506f0b33-0003'), 2.5);\n freq = $bm_div(effect('Excite - Position - Transform')('Pseudo/BNCA2506f0b33-0004'), 20);\n decay = $bm_div(effect('Excite - Position - Transform')('Pseudo/BNCA2506f0b33-0005'), 20);\n n = 0, 0 < numKeys && (n = nearestKey(time).index, key(n).time > time && n--), t = 0 === n ? 0 : $bm_sub(time, key(n).time), $bm_rt = 0 < n ? (v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10))), $bm_sum(value, $bm_div($bm_mul($bm_mul($bm_div(v, 100), amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))))) : value;\n }\n} catch (err) {\n $bm_rt = value = value;\n}"},"a":{"a":0,"k":[0,0],"ix":1,"x":"var $bm_rt;\nvar enable, amp, freq, decay, n, t, v;\ntry {\n $bm_rt = enable = effect('Excite - Anchor Point - Transform')('Pseudo/BNCA2506f0b33-0001');\n if (enable == 0) {\n $bm_rt = value;\n } else {\n amp = $bm_div(effect('Excite - Anchor Point - Transform')('Pseudo/BNCA2506f0b33-0003'), 2.5);\n freq = $bm_div(effect('Excite - Anchor Point - Transform')('Pseudo/BNCA2506f0b33-0004'), 20);\n decay = $bm_div(effect('Excite - Anchor Point - Transform')('Pseudo/BNCA2506f0b33-0005'), 20);\n n = 0, 0 < numKeys && (n = nearestKey(time).index, key(n).time > time && n--), t = 0 === n ? 0 : $bm_sub(time, key(n).time), $bm_rt = 0 < n ? (v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10))), $bm_sum(value, $bm_div($bm_mul($bm_mul($bm_div(v, 100), amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))))) : value;\n }\n} catch (err) {\n $bm_rt = value = value;\n}"},"s":{"a":0,"k":[99,99],"ix":3,"x":"var $bm_rt;\nvar enable, amp, freq, decay, n, t, v;\ntry {\n $bm_rt = enable = effect('Excite - Scale - Transform')('Pseudo/BNCA2506f0b33-0001');\n if (enable == 0) {\n $bm_rt = value;\n } else {\n amp = $bm_div(effect('Excite - Scale - Transform')('Pseudo/BNCA2506f0b33-0003'), 2.5);\n freq = $bm_div(effect('Excite - Scale - Transform')('Pseudo/BNCA2506f0b33-0004'), 20);\n decay = $bm_div(effect('Excite - Scale - Transform')('Pseudo/BNCA2506f0b33-0005'), 20);\n n = 0, 0 < numKeys && (n = nearestKey(time).index, key(n).time > time && n--), t = 0 === n ? 0 : $bm_sub(time, key(n).time), $bm_rt = 0 < n ? (v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10))), $bm_sum(value, $bm_div($bm_mul($bm_mul($bm_div(v, 100), amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))))) : value;\n }\n} catch (err) {\n $bm_rt = value = value;\n}"},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4,"x":"var $bm_rt;\nvar enable, amp, freq, decay, n, t, v;\ntry {\n $bm_rt = enable = effect('Excite - Skew - Transform')('Pseudo/BNCA2506f0b33-0001');\n if (enable == 0) {\n $bm_rt = value;\n } else {\n amp = $bm_div(effect('Excite - Skew - Transform')('Pseudo/BNCA2506f0b33-0003'), 2.5);\n freq = $bm_div(effect('Excite - Skew - Transform')('Pseudo/BNCA2506f0b33-0004'), 20);\n decay = $bm_div(effect('Excite - Skew - Transform')('Pseudo/BNCA2506f0b33-0005'), 20);\n n = 0, 0 < numKeys && (n = nearestKey(time).index, key(n).time > time && n--), t = 0 === n ? 0 : $bm_sub(time, key(n).time), $bm_rt = 0 < n ? (v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10))), $bm_sum(value, $bm_div($bm_mul($bm_mul($bm_div(v, 100), amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))))) : value;\n }\n} catch (err) {\n $bm_rt = value = value;\n}"},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":42,"s":[100]},{"t":58,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"circle - stroke","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[100]},{"t":36,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[617.721,614.58,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[172.277,0],[0,-172.277],[-172.277,0],[0,172.277]],"o":[[-172.277,0],[0,172.277],[172.277,0],[0,-172.277]],"v":[[0,-311.936],[-311.936,0],[0,311.936],[311.936,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.22745098039215686,0.788235294117647,0.40784313725490196,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-17.721,-14.58],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[130.536,130.536],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[100]},{"t":23,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"circle - bg","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[617.585,614.469,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.5],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":19,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":33,"s":[114.99999999999999,114.99999999999999,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":40,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":45,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":114,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":130,"s":[110.00000000000001,110.00000000000001,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":136,"s":[95,95,100]},{"t":142,"s":[105,105,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[172.277,0],[0,-172.277],[-172.277,0],[0,172.277]],"o":[[-172.277,0],[0,172.277],[172.277,0],[0,-172.277]],"v":[[0,-311.936],[-311.936,0],[0,311.936],[311.936,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.22745098039215686,0.788235294117647,0.40784313725490196,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-17.721,-14.58],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[130.536,130.536],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"burst","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[612.759,610.498,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[72,72,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[172.277,0],[0,-172.277],[-172.277,0],[0,172.277]],"o":[[-172.277,0],[0,172.277],[172.277,0],[0,-172.277]],"v":[[0,-311.936],[-311.936,0],[0,311.936],[311.936,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.22745098039215686,0.788235294117647,0.40784313725490196,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":30,"s":[40]},{"t":68,"s":[0]}],"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":0,"ix":1}},{"n":"g","nm":"gap","v":{"a":0,"k":0,"ix":2}},{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-17.721,-14.58],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":26,"s":[167.536,167.536]},{"t":68,"s":[251.536,251.536]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":26,"s":[0]},{"t":34,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"burst 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[612.759,610.498,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[72,72,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[172.277,0],[0,-172.277],[-172.277,0],[0,172.277]],"o":[[-172.277,0],[0,172.277],[172.277,0],[0,-172.277]],"v":[[0,-311.936],[-311.936,0],[0,311.936],[311.936,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.22745098039215686,0.788235294117647,0.40784313725490196,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":122,"s":[40]},{"t":170,"s":[0]}],"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":0,"ix":1}},{"n":"g","nm":"gap","v":{"a":0,"k":0,"ix":2}},{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-17.721,-14.58],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":118,"s":[167.536,167.536]},{"t":170,"s":[251.536,251.536]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":124,"s":[0]},{"t":132,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/animations/error-warn.json b/assets/animations/error-warn.json new file mode 100644 index 00000000..d5173f13 --- /dev/null +++ b/assets/animations/error-warn.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":30,"ip":0,"op":80,"w":500,"h":500,"nm":"exclamação animation","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"exclamation","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":7,"s":[258.4,231.36,0],"to":[0,-1.06,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[258.4,225,0],"to":[0,0,0],"ti":[0,-1.06,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":13,"s":[258.4,231.36,0],"to":[0,1.06,0],"ti":[0,1.06,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":41,"s":[258.4,231.36,0],"to":[0,-1.06,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":44,"s":[258.4,225,0],"to":[0,0,0],"ti":[0,-1.06,0]},{"t":47,"s":[258.4,231.36,0]}],"ix":2},"a":{"a":0,"k":[16.613,83.692,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[90,90,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[93,93,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":13,"s":[90,90,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":41,"s":[90,90,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44,"s":[93,93,100]},{"t":47,"s":[90,90,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.037,0],[0,0],[0,9.038],[-9.037,0],[0,-9.037]],"o":[[0,0],[-9.037,0],[0,-9.037],[9.037,0],[0,9.038]],"v":[[0,16.363],[0,16.363],[-16.363,0],[0,-16.363],[16.363,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.613,150.771],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[8.134,0],[0,0],[0,8.134],[0,0],[-8.133,0],[0,-8.134],[0,0]],"o":[[0,0],[-8.133,0],[0,0],[0,-8.134],[8.134,0],[0,0],[0,8.134]],"v":[[0,59.999],[0,59.999],[-14.727,45.271],[-14.727,-45.271],[0,-59.999],[14.727,-45.271],[14.727,45.271]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.613,60.249],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":80,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"stroke circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[258.4,231.36,0],"ix":2},"a":{"a":0,"k":[146,146,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-77.872,0],[0,-77.872],[77.872,0],[0,77.872]],"o":[[77.872,0],[0,77.872],[-77.872,0],[0,-77.872]],"v":[[0,-141],[141,0],[0,141],[-141,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.952999997606,0.340999977261,0.301999978458,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[146,146],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":30,"s":[90,90]},{"t":78,"s":[120,120]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[90]},{"t":79,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"stroke circle 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[258.4,231.36,0],"ix":2},"a":{"a":0,"k":[146,146,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-77.872,0],[0,-77.872],[77.872,0],[0,77.872]],"o":[[77.872,0],[0,77.872],[-77.872,0],[0,-77.872]],"v":[[0,-141],[141,0],[0,141],[-141,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.952999997606,0.340999977261,0.301999978458,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[146,146],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":0,"s":[90,90]},{"t":48,"s":[120,120]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[90]},{"t":49,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[258.4,231.36,0],"ix":2},"a":{"a":0,"k":[130.82,130.82,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-72.112,0],[0,-72.112],[72.111,0],[0,72.111]],"o":[[72.111,0],[0,72.111],[-72.112,0],[0,-72.112]],"v":[[0,-130.57],[130.57,0],[0,130.57],[-130.57,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.952999997606,0.340999977261,0.301999978458,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.82,130.82],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/animations/error.json b/assets/animations/error.json new file mode 100644 index 00000000..c3095b05 --- /dev/null +++ b/assets/animations/error.json @@ -0,0 +1,2074 @@ +{ + "v": "4.6.3", + "fr": 24, + "ip": 0, + "op": 21, + "w": 320, + "h": 320, + "nm": "checklist", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 13", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 300 }, + "p": { "a": 0, "k": [160, 159.5, 0] }, + "a": { "a": 0, "k": [0, -34, 0] }, + "s": { "a": 0, "k": [100, 100, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p667_1_0p167_0p167", "0p667_1_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 17 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p667_1_0p167_0p167", + "t": 6, + "s": [-8.142, -92.147], + "e": [-7.675, -162.544], + "to": [0.07779947668314, -11.7327470779419], + "ti": [-0.07779947668314, 11.7327470779419] + }, + { "t": 17 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.981, 100], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p667_1_0p167_0p167"], + "t": 6, + "s": [20.367], + "e": [6.367] + }, + { "t": 17 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833], "y": [0.833, 0.833] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p833_0p833_0p167_0p167", "0p833_0p833_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 21 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.833, "y": 0.833 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p833_0p833_0p167_0p167", + "t": 6, + "s": [16.585, -99.759], + "e": [28.521, -187.495], + "to": [1.9892578125, -14.6227216720581], + "ti": [-1.9892578125, 14.6227216720581] + }, + { "t": 21 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.419, 116], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 6, + "s": [14.733], + "e": [8.733] + }, + { "t": 21 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Shape Layer 12", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 250 }, + "p": { "a": 0, "k": [160, 159.5, 0] }, + "a": { "a": 0, "k": [0, -34, 0] }, + "s": { "a": 0, "k": [100, 100, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p667_1_0p167_0p167", "0p667_1_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 17 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p667_1_0p167_0p167", + "t": 6, + "s": [-8.142, -92.147], + "e": [-7.675, -162.544], + "to": [0.07779947668314, -11.7327470779419], + "ti": [-0.07779947668314, 11.7327470779419] + }, + { "t": 17 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.981, 100], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p667_1_0p167_0p167"], + "t": 6, + "s": [20.367], + "e": [6.367] + }, + { "t": 17 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833], "y": [0.833, 0.833] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p833_0p833_0p167_0p167", "0p833_0p833_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 21 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.833, "y": 0.833 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p833_0p833_0p167_0p167", + "t": 6, + "s": [16.585, -99.759], + "e": [28.521, -187.495], + "to": [1.9892578125, -14.6227216720581], + "ti": [-1.9892578125, 14.6227216720581] + }, + { "t": 21 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.419, 116], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 6, + "s": [14.733], + "e": [8.733] + }, + { "t": 21 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Shape Layer 11", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 200 }, + "p": { "a": 0, "k": [160, 159.5, 0] }, + "a": { "a": 0, "k": [0, -34, 0] }, + "s": { "a": 0, "k": [100, 100, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p667_1_0p167_0p167", "0p667_1_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 17 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p667_1_0p167_0p167", + "t": 6, + "s": [-8.142, -92.147], + "e": [-7.675, -162.544], + "to": [0.07779947668314, -11.7327470779419], + "ti": [-0.07779947668314, 11.7327470779419] + }, + { "t": 17 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.981, 100], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p667_1_0p167_0p167"], + "t": 6, + "s": [20.367], + "e": [6.367] + }, + { "t": 17 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833], "y": [0.833, 0.833] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p833_0p833_0p167_0p167", "0p833_0p833_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 21 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.833, "y": 0.833 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p833_0p833_0p167_0p167", + "t": 6, + "s": [16.585, -99.759], + "e": [28.521, -187.495], + "to": [1.9892578125, -14.6227216720581], + "ti": [-1.9892578125, 14.6227216720581] + }, + { "t": 21 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.419, 116], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 6, + "s": [14.733], + "e": [8.733] + }, + { "t": 21 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "Shape Layer 10", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 150 }, + "p": { "a": 0, "k": [160, 159.5, 0] }, + "a": { "a": 0, "k": [0, -34, 0] }, + "s": { "a": 0, "k": [100, 100, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p667_1_0p167_0p167", "0p667_1_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 17 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p667_1_0p167_0p167", + "t": 6, + "s": [-8.142, -92.147], + "e": [-7.675, -162.544], + "to": [0.07779947668314, -11.7327470779419], + "ti": [-0.07779947668314, 11.7327470779419] + }, + { "t": 17 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.981, 100], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p667_1_0p167_0p167"], + "t": 6, + "s": [20.367], + "e": [6.367] + }, + { "t": 17 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833], "y": [0.833, 0.833] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p833_0p833_0p167_0p167", "0p833_0p833_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 21 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.833, "y": 0.833 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p833_0p833_0p167_0p167", + "t": 6, + "s": [16.585, -99.759], + "e": [28.521, -187.495], + "to": [1.9892578125, -14.6227216720581], + "ti": [-1.9892578125, 14.6227216720581] + }, + { "t": 21 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.419, 116], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 6, + "s": [14.733], + "e": [8.733] + }, + { "t": 21 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "Shape Layer 9", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 100 }, + "p": { "a": 0, "k": [160, 159.5, 0] }, + "a": { "a": 0, "k": [0, -34, 0] }, + "s": { "a": 0, "k": [100, 100, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p667_1_0p167_0p167", "0p667_1_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 17 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p667_1_0p167_0p167", + "t": 6, + "s": [-8.142, -92.147], + "e": [-7.675, -162.544], + "to": [0.07779947668314, -11.7327470779419], + "ti": [-0.07779947668314, 11.7327470779419] + }, + { "t": 17 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.981, 100], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p667_1_0p167_0p167"], + "t": 6, + "s": [20.367], + "e": [6.367] + }, + { "t": 17 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833], "y": [0.833, 0.833] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p833_0p833_0p167_0p167", "0p833_0p833_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 21 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.833, "y": 0.833 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p833_0p833_0p167_0p167", + "t": 6, + "s": [16.585, -99.759], + "e": [28.521, -187.495], + "to": [1.9892578125, -14.6227216720581], + "ti": [-1.9892578125, 14.6227216720581] + }, + { "t": 21 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.419, 116], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 6, + "s": [14.733], + "e": [8.733] + }, + { "t": 21 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "Shape Layer 8", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 50 }, + "p": { "a": 0, "k": [160, 159.5, 0] }, + "a": { "a": 0, "k": [0, -34, 0] }, + "s": { "a": 0, "k": [100, 100, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p667_1_0p167_0p167", "0p667_1_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 17 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p667_1_0p167_0p167", + "t": 6, + "s": [-8.142, -92.147], + "e": [-7.675, -162.544], + "to": [0.07779947668314, -11.7327470779419], + "ti": [-0.07779947668314, 11.7327470779419] + }, + { "t": 17 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.981, 100], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p667_1_0p167_0p167"], + "t": 6, + "s": [20.367], + "e": [6.367] + }, + { "t": 17 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833], "y": [0.833, 0.833] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p833_0p833_0p167_0p167", "0p833_0p833_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 21 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.833, "y": 0.833 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p833_0p833_0p167_0p167", + "t": 6, + "s": [16.585, -99.759], + "e": [28.521, -187.495], + "to": [1.9892578125, -14.6227216720581], + "ti": [-1.9892578125, 14.6227216720581] + }, + { "t": 21 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.419, 116], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 6, + "s": [14.733], + "e": [8.733] + }, + { "t": 21 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 8, + "ty": 4, + "nm": "Shape Layer 7", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [160, 159.5, 0] }, + "a": { "a": 0, "k": [0, -34, 0] }, + "s": { "a": 0, "k": [100, 100, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p667_1_0p167_0p167", "0p667_1_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 17 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p667_1_0p167_0p167", + "t": 6, + "s": [-8.142, -92.147], + "e": [-7.675, -162.544], + "to": [0.07779947668314, -11.7327470779419], + "ti": [-0.07779947668314, 11.7327470779419] + }, + { "t": 17 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.981, 100], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p667_1_0p167_0p167"], + "t": 6, + "s": [20.367], + "e": [6.367] + }, + { "t": 17 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833], "y": [0.833, 0.833] }, + "o": { "x": [0.167, 0.167], "y": [0.167, 0.167] }, + "n": ["0p833_0p833_0p167_0p167", "0p833_0p833_0p167_0p167"], + "t": 6, + "s": [15.021, 15.021], + "e": [0, 0] + }, + { "t": 21 } + ] + }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 17 } + ] + }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.833, "y": 0.833 }, + "o": { "x": 0.167, "y": 0.167 }, + "n": "0p833_0p833_0p167_0p167", + "t": 6, + "s": [16.585, -99.759], + "e": [28.521, -187.495], + "to": [1.9892578125, -14.6227216720581], + "ti": [-1.9892578125, 14.6227216720581] + }, + { "t": 21 } + ], + "ix": 2 + }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.419, 116], "ix": 3 }, + "r": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 6, + "s": [14.733], + "e": [8.733] + }, + { "t": 21 } + ], + "ix": 6 + }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 9, + "ty": 4, + "nm": "Shape Layer 5", + "parent": 11, + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [0.8, -0.641, 0] }, + "a": { "a": 0, "k": [0, 0, 0] }, + "s": { "a": 0, "k": [7.39, 7.39, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [0, 0] + ], + "o": [ + [0, 0], + [0, 0] + ], + "v": [ + [-6.977, 7], + [6.973, -7] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [0, 0] + ], + "o": [ + [0, 0], + [0, 0] + ], + "v": [ + [-6.977, 7], + [6.973, -7] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [0, 0], + "ix": 2 + }, + "a": { + "a": 0, + "k": [0, 0], + "ix": 1 + }, + "s": { + "a": 0, + "k": [100, 100], + "ix": 3 + }, + "r": { + "a": 0, + "k": -90, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 1, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "mm", + "mm": 2, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [1, 1, 1, 1], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 4, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [0, 0], + "ix": 2 + }, + "a": { + "a": 0, + "k": [0, 0], + "ix": 1 + }, + "s": { + "a": 0, + "k": [200, 200], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Combined Shape", + "np": 4, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 7, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 10, + "ty": 4, + "nm": "Shape Layer 6", + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 4, + "s": [50], + "e": [0] + }, + { "t": 14 } + ] + }, + "r": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [160, 160, 0] }, + "a": { "a": 0, "k": [0, 0, 0] }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 0.667] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0.333] }, + "n": ["0p667_1_0p333_0", "0p667_1_0p333_0", "0p667_0p667_0p333_0p333"], + "t": 4, + "s": [100, 100, 100], + "e": [1085, 1085, 100] + }, + { "t": 14 } + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [19.779, 19.779] }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { "a": 0, "k": [0.8274509, 0.1843137, 0.1843137, 1] }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-0.068, 0.036], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + } + ], + "ip": 4, + "op": 22, + "st": -23, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 11, + "ty": 4, + "nm": "Shape Layer 4", + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 6, + "s": [30], + "e": [100] + }, + { "t": 9 } + ] + }, + "r": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [160.312, 161.188, 0] }, + "a": { "a": 0, "k": [0.812, -0.562, 0] }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 0.667] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0.333] }, + "n": ["0p667_1_0p333_0", "0p667_1_0p333_0", "0p667_0p667_0p333_0p333"], + "t": 6, + "s": [100, 100, 100], + "e": [1087, 1087, 100] + }, + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 0.667] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0.333] }, + "n": ["0p667_1_0p333_0", "0p667_1_0p333_0", "0p667_0p667_0p333_0p333"], + "t": 11, + "s": [1087, 1087, 100], + "e": [866, 866, 100] + }, + { + "i": { "x": [0.833, 0.833, 0.833], "y": [0.833, 0.833, 0.833] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0.333] }, + "n": ["0p833_0p833_0p333_0", "0p833_0p833_0p333_0", "0p833_0p833_0p333_0p333"], + "t": 13, + "s": [866, 866, 100], + "e": [878, 878, 100] + }, + { "t": 16 } + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [10.068, 10.068] }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "fl", + "c": { "a": 0, "k": [0.8274509, 0.1843137, 0.1843137, 1] }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0.784, -0.716], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + } + ], + "ip": 6, + "op": 22, + "st": -19, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 12, + "ty": 4, + "nm": "Shape Layer 3", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [161, 160, 0] }, + "a": { "a": 0, "k": [0, 0, 0] }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 0.667] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0.333] }, + "n": ["0p667_1_0p333_0", "0p667_1_0p333_0", "0p667_0p667_0p333_0p333"], + "t": 3, + "s": [100, 100, 100], + "e": [224, 224, 100] + }, + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 0.667] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0.333] }, + "n": ["0p667_1_0p333_0", "0p667_1_0p333_0", "0p667_0p667_0p333_0p333"], + "t": 4, + "s": [224, 224, 100], + "e": [476, 476, 100] + }, + { "t": 8 } + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [6.009, 6.009] }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "st", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 4, + "s": [0.8274509, 0.1843137, 0.1843137, 1], + "e": [0.8274509, 0.1843137, 0.1843137, 1] + }, + { "t": 8 } + ] + }, + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 4, + "s": [0], + "e": [100] + }, + { "t": 5 } + ] + }, + "w": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 4, + "s": [3], + "e": [0] + }, + { "t": 8 } + ] + }, + "lc": 1, + "lj": 1, + "ml": 4, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke" + }, + { + "ty": "fl", + "c": { "a": 0, "k": [0.8274509, 0.1843137, 0.1843137, 1] }, + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 3, + "s": [100], + "e": [99] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 4, + "s": [99], + "e": [0] + }, + { "t": 5 } + ] + }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-0.338, 0.065], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [649.112, 649.112], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 2", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + } + ], + "ip": 3, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + }, + { + "ddd": 0, + "ind": 13, + "ty": 4, + "nm": "Shape Layer 2", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [160.142, 159.987, 0] }, + "a": { "a": 0, "k": [0, 0, 0] }, + "s": { "a": 0, "k": [377.603, 377.603, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [22.315, 22.315] }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "st", + "c": { "a": 0, "k": [0.8352941, 0.8352941, 0.8352941, 1] }, + "o": { "a": 0, "k": 100 }, + "w": { "a": 0, "k": 1 }, + "lc": 1, + "lj": 1, + "ml": 4, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke" + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1, 1] }, + "o": { "a": 0, "k": 100 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill" + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-0.038, 0.003], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + } + ], + "ip": -21, + "op": 22, + "st": -21, + "bm": 0, + "sr": 1 + } + ] +} diff --git a/assets/animations/loading.json b/assets/animations/loading.json new file mode 100644 index 00000000..b0e2812b --- /dev/null +++ b/assets/animations/loading.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":30,"ip":0,"op":60,"w":300,"h":300,"nm":"loading_6","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":60,"s":[360]}],"ix":10},"p":{"a":0,"k":[150.00000000000003,150.00000000000003,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[30.000000000000004,30.000000000000004,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[300,300],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":50,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":60,"s":[99]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[1]},{"t":50,"s":[100]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":60,"s":[3]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":30,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150.00000000000003,150.00000000000003,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[30.000000000000004,30.000000000000004,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[300,300],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.4470588235294118,0.4470588235294118,0.4470588235294118,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":50,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/animations/nfc-mood.json b/assets/animations/nfc-mood.json new file mode 100644 index 00000000..82a94a27 --- /dev/null +++ b/assets/animations/nfc-mood.json @@ -0,0 +1 @@ +{"v":"5.4.4","fr":25,"ip":0,"op":137,"w":612,"h":382,"nm":"read_card","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 2","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[274,-75,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":137,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"elips NFC ","parent":1,"sr":1,"ks":{"o":{"a":0,"k":60,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[264.25,201,0],"e":[54,449,0],"to":[-35.042,41.333,0],"ti":[35.042,-41.333,0]},{"t":9}],"ix":2},"a":{"a":0,"k":[360,640,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[32.362,31.264,100],"e":[103.512,100,100]},{"t":9}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-48.877],[69.824,0],[0,48.877],[-69.825,0]],"o":[[0,48.877],[-69.825,0],[0,-48.877],[69.824,0]],"v":[[126.428,0],[0,88.5],[-126.428,0],[0,-88.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[341.4,452.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":200,"op":200,"st":0,"bm":0,"hidden":0},{"ddd":0,"ind":3,"ty":4,"nm":"wave 1","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":17,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":32,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":39,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":62,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":65,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":78,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":93,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":96,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[60],"e":[60]},{"t":123}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":9,"s":[44,449,0],"e":[54,449,0],"to":[1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":17,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":35,"s":[54,449,0],"e":[44,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":47,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[54,449,0],"e":[44,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[54,449,0],"e":[44,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":108,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"t":126}],"ix":2},"a":{"a":0,"k":[360,640,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-5.693],[2.915,-4.592]],"o":[[3.175,4.725],[0,5.439],[0,0]],"v":[[-2.436,-15.672],[2.436,0.312],[-2.028,15.672]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[295.136,453.031],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":9,"op":200,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"wave 2","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":20,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":32,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":35,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":42,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":50,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":62,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":65,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":73,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":81,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":93,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":96,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":103,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":111,"s":[60],"e":[60]},{"t":123}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[44,449,0],"e":[54,449,0],"to":[1.667,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":20,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":35,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":42,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":50,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":65,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":73,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":81,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":96,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":103,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":111,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"t":126}],"ix":2},"a":{"a":0,"k":[360,640,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-10.142],[5.103,-8.255]],"o":[[5.542,8.493],[0,9.705],[0,0]],"v":[[-4.247,-28.018],[4.247,0.543],[-3.56,28.018]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[316.924,452.801],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":12,"op":200,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"wave 3 ","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":32,"s":[60],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":35,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":45,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":53,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":62,"s":[60],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":65,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":76,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":84,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":93,"s":[60],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":96,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":106,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":114,"s":[60],"e":[60]},{"t":123}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":15,"s":[44,449,0],"e":[54,449,0],"to":[1.667,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":23,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":53,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":84,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":96,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":106,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":114,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"t":126}],"ix":2},"a":{"a":0,"k":[360,640,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-15.048],[7.571,-12.25]],"o":[[8.225,12.602],[0,14.401],[0,0]],"v":[[-6.302,-41.575],[6.302,0.804],[-5.281,41.575]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[340.155,452.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":15,"op":200,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"wave 4","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":18,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":26,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":32,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":35,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":48,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":56,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":62,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":65,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":79,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[60],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":93,"s":[60],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":96,"s":[0],"e":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":109,"s":[0],"e":[60]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":117,"s":[60],"e":[60]},{"t":123}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[44,449,0],"e":[54,449,0],"to":[1.667,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":26,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":35,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":56,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":65,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":79,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":87,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":96,"s":[54,449,0],"e":[44,449,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":109,"s":[44,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":117,"s":[54,449,0],"e":[54,449,0],"to":[0,0,0],"ti":[0,0,0]},{"t":126}],"ix":2},"a":{"a":0,"k":[360,640,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-20.283],[10.205,-16.511]],"o":[[11.085,16.986],[0,19.41],[0,0]],"v":[[-8.495,-56.036],[8.494,1.083],[-7.118,56.036]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[364.934,452.259],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":18,"op":200,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/animations/phone-scanning.json b/assets/animations/phone-scanning.json new file mode 100644 index 00000000..bc053a3b --- /dev/null +++ b/assets/animations/phone-scanning.json @@ -0,0 +1 @@ +{"v":"5.6.3","fr":60,"ip":0,"op":120,"w":800,"h":600,"nm":"2-white-800x600","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"phone-speaker","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.655,-23,0],"ix":2},"a":{"a":0,"k":[0.655,-22.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.34,"y":1},"o":{"x":0.884,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-20.621,-23.25],[21.93,-23.25]],"c":false}]},{"i":{"x":0.34,"y":1},"o":{"x":0.884,"y":0},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-14.496,-0.5],[15.805,-0.5]],"c":false}]},{"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-20.621,-23.25],[21.93,-23.25]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.884],"y":[0]},"t":0,"s":[6]},{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.884],"y":[0]},"t":60,"s":[4]},{"t":120,"s":[6]}],"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"phone-screen","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-9.598,0],"ix":2},"a":{"a":0,"k":[0,-9.598,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.34,"y":1},"o":{"x":0.884,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-66.235,-9.598],[66.236,-9.598]],"c":false}]},{"i":{"x":0.34,"y":1},"o":{"x":0.884,"y":0},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-61.86,12.277],[61.861,12.277]],"c":false}]},{"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-66.235,-9.598],[66.236,-9.598]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.884],"y":[0]},"t":0,"s":[8]},{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.884],"y":[0]},"t":60,"s":[4]},{"t":120,"s":[8]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"phone-mask","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,200,0],"ix":2},"a":{"a":0,"k":[-467.836,-2.947,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-82.843,0],[0,-82.843],[82.843,0],[0,82.843]],"o":[[82.843,0],[0,82.843],[-82.843,0],[0,-82.843]],"v":[[-467.836,-152.947],[-317.836,-2.947],[-467.836,147.053],[-617.836,-2.947]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Oval","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"phone-outline","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,414,0],"ix":2},"a":{"a":0,"k":[0,214,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[-82.843,0],[0,-82.843],[82.843,0],[0,82.843]],"o":[[82.843,0],[0,82.843],[-82.843,0],[0,-82.843]],"v":[[0.354,-150.342],[150.354,-0.342],[0.354,149.658],[-149.646,-0.342]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.34,"y":1},"o":{"x":0.884,"y":0},"t":0,"s":[{"i":[[8.455,0],[0,0],[0,8.455],[0,0],[-8.455,0],[0,0],[0,-8.455],[0,0]],"o":[[0,0],[-8.455,0],[0,0],[0,-8.455],[0,0],[8.455,0],[0,0],[0,8.455]],"v":[[53.691,210],[-53.691,210],[-69,194.691],[-69,-20.691],[-53.691,-36],[53.691,-36],[69,-20.691],[69,194.691]],"c":true}]},{"i":{"x":0.34,"y":1},"o":{"x":0.884,"y":0},"t":60,"s":[{"i":[[8.455,0],[0,0],[0,8.455],[0,0],[-8.455,0],[0,0],[0,-8.455],[0,0]],"o":[[0,0],[-8.455,0],[0,0],[0,-8.455],[0,0],[8.455,0],[0,0],[0,8.455]],"v":[[53.691,210],[-53.691,210],[-69,194.691],[-59,6.121],[-43.691,-9.188],[43.691,-9.188],[59,6.121],[69,194.691]],"c":true}]},{"t":120,"s":[{"i":[[8.455,0],[0,0],[0,8.455],[0,0],[-8.455,0],[0,0],[0,-8.455],[0,0]],"o":[[0,0],[-8.455,0],[0,0],[0,-8.455],[0,0],[8.455,0],[0,0],[0,8.455]],"v":[[53.691,210],[-53.691,210],[-69,194.691],[-69,-20.691],[-53.691,-36],[53.691,-36],[69,-20.691],[69,194.691]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"phone-shine","sr":1,"ks":{"o":{"a":0,"k":32,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[207,286.5,0],"ix":2},"a":{"a":0,"k":[7,86.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[-82.843,0],[0,-82.843],[82.843,0],[0,82.843]],"o":[[82.843,0],[0,82.843],[-82.843,0],[0,-82.843]],"v":[[0.354,-150.342],[150.354,-0.342],[0.354,149.658],[-149.646,-0.342]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.884,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[66,-12],[67,64.5],[68.75,-12.5],[66.164,-12.03]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":41,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-62.682,4.808],[67,138],[64.808,5.91],[-57.847,5.279]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":47,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-61.389,30.725],[67,138],[61.751,7.934],[-62.269,6.757]],"c":true}]},{"i":{"x":0.34,"y":1},"o":{"x":0.167,"y":0.167},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-60.527,48.003],[67,138],[59.712,9.283],[-60.05,7.584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.884,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-60.25,53],[67,138],[59,11],[-59.749,9.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-60.527,48.003],[67,138],[59.712,9.283],[-60.05,7.584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-61.389,30.725],[67,138],[61.751,7.934],[-62.269,6.757]],"c":true}]},{"i":{"x":0.34,"y":1},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-62.682,4.808],[67,138],[64.808,5.91],[-57.847,5.279]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[66,-12],[67,138],[68.75,-12.5],[66.164,-12.03]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"outline","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"p":{"a":0,"k":[200,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[308,308],"ix":2,"x":"var $bm_rt;\n$bm_rt = content('Group 1').content('Ellipse Path 1').size;"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.996078431372549,0.8666666666666667,0.7725490196078432,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":120,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"1-white","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[200,200,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":400,"h":400,"ip":0,"op":120,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/animations/qr-code.json b/assets/animations/qr-code.json new file mode 100644 index 00000000..991f8a8d --- /dev/null +++ b/assets/animations/qr-code.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.9","a":"","k":"","d":"","tc":""},"fr":30,"ip":0,"op":90,"w":300,"h":300,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 24","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[276.875,278,0],"ix":2},"a":{"a":0,"k":[-130.5,-133,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-130.5,-67],[-130.5,-133],[-64.5,-132.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[0]},{"t":50,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-1,"op":90,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 23","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[24.5,276.5,0],"ix":2},"a":{"a":0,"k":[-130.5,-133,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-130.5,-67],[-130.5,-133],[-64.5,-132.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[0]},{"t":45,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":91,"st":-5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 22","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[276.875,24,0],"ix":2},"a":{"a":0,"k":[-130.5,-133,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-130.5,-67],[-130.5,-133],[-64.5,-132.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":40,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-10,"op":90,"st":-10,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[24.5,24,0],"ix":2},"a":{"a":0,"k":[-130.5,-133,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-130.5,-67],[-130.5,-133],[-64.5,-132.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":35,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-15,"op":90,"st":-15,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 20","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[11,-88],[11,-66.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 19","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-10.75,-66],[-10.5,-104.25],[32,-104.25],[32,-67.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 18","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[70.5,-17.5],[70.5,16],[88.875,16]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 17","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-108.75,-11.5],[14.75,-11.5],[15,15.25],[-109.25,15.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":25,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":5,"op":95,"st":5,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":1,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[66.75,-31.75],[104.5,-31.75],[104.5,39.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":35,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":15,"op":105,"st":15,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-108.75,35.5],[-46.125,35.625]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":25,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":5,"op":95,"st":5,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-9.5,48.5],[-9.438,93.125]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":30,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":100,"st":10,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-30,93.5],[-30,36],[-5,36]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.75,-36],[51.75,20.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[8.25,35.5],[80,35.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":25,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":5,"op":95,"st":5,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-108.75,-33.625],[20,-33.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,2],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[148.75,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[33.75,48],[33.75,109.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":35,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":15,"op":105,"st":15,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-34,106],[13,106],[13,48.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-31.5,-108.75],[-31.5,-50.875],[34.5,-50.875],[34.5,20.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":30,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":100,"st":10,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[228.125,229.25,0],"ix":2},"a":{"a":0,"k":[-86,-79,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[9.125,9.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86.438,-79.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[53,53],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86,-79],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[228.125,72.25,0],"ix":2},"a":{"a":0,"k":[-86,-79,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[9.125,9.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86.438,-79.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[53,53],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86,-79],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":25,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":5,"op":95,"st":5,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[72.125,229.25,0],"ix":2},"a":{"a":0,"k":[-86,-79,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[9.125,9.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86.438,-79.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[53,53],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86,-79],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[72.125,72.25,0],"ix":2},"a":{"a":0,"k":[-86,-79,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[9.125,9.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86.438,-79.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[53,53],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-86,-79],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":25,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":5,"op":95,"st":5,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/animations/read-nfc-done.json b/assets/animations/read-nfc-done.json new file mode 100644 index 00000000..6f887f37 --- /dev/null +++ b/assets/animations/read-nfc-done.json @@ -0,0 +1,3430 @@ +{ + "v": "5.5.7", + "meta": { + "g": "LottieFiles AE 0.1.20", + "a": "", + "k": "", + "d": "", + "tc": "none" + }, + "fr": 29.9700012207031, + "ip": 0, + "op": 210.000008553475, + "w": 1080, + "h": 1080, + "nm": "wifi", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "wifi/sdk_elements_02 Outlines 3", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 205, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 209, + "s": [ + 0 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 235, + "s": [ + 100 + ] + }, + { + "t": 239.00000973467, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 540, + 540, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 70.5, + 70.5, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + -38.66 + ], + [ + 38.66, + 0 + ], + [ + 0, + 38.66 + ], + [ + -38.66, + 0 + ] + ], + "o": [ + [ + 0, + 38.66 + ], + [ + -38.66, + 0 + ], + [ + 0, + -38.66 + ], + [ + 38.66, + 0 + ] + ], + "v": [ + [ + 70, + 0 + ], + [ + 0, + 70 + ], + [ + -70, + 0 + ], + [ + 0, + -70 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 59, + "s": [ + 0 + ] + }, + { + "t": 120.0000048877, + "s": [ + 100 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.819607913494, + 0.007843137719, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 4, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 70.25, + 70.25 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 5", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gs", + "o": { + "a": 0, + "k": 100, + "ix": 9 + }, + "w": { + "a": 0, + "k": 2, + "ix": 10 + }, + "g": { + "p": 2, + "k": { + "a": 0, + "k": [ + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0 + ], + "ix": 8 + } + }, + "s": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 4 + }, + "e": { + "a": 0, + "k": [ + 100, + 0 + ], + "ix": 5 + }, + "t": 1, + "lc": 1, + "lj": 1, + "ml": 4, + "ml2": { + "a": 0, + "k": 4, + "ix": 13 + }, + "bm": 0, + "nm": "Gradient Stroke 1", + "mn": "ADBE Vector Graphic - G-Stroke", + "hd": false + } + ], + "ip": 60.0000024438501, + "op": 210.000008553475, + "st": -31.0000012626559, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "wifi/sdk_elements_02 Outlines", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 540, + 540, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 70.5, + 70.5, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + -38.66 + ], + [ + 38.66, + 0 + ], + [ + 0, + 38.66 + ], + [ + -38.66, + 0 + ] + ], + "o": [ + [ + 0, + 38.66 + ], + [ + -38.66, + 0 + ], + [ + 0, + -38.66 + ], + [ + 38.66, + 0 + ] + ], + "v": [ + [ + 70, + 0 + ], + [ + 0, + 70 + ], + [ + -70, + 0 + ], + [ + 0, + -70 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.866666734219, + 0.866666734219, + 0.866666734219, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 4, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 70.25, + 70.25 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 5", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gs", + "o": { + "a": 0, + "k": 100, + "ix": 9 + }, + "w": { + "a": 0, + "k": 2, + "ix": 10 + }, + "g": { + "p": 2, + "k": { + "a": 0, + "k": [ + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0 + ], + "ix": 8 + } + }, + "s": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 4 + }, + "e": { + "a": 0, + "k": [ + 100, + 0 + ], + "ix": 5 + }, + "t": 1, + "lc": 1, + "lj": 1, + "ml": 4, + "ml2": { + "a": 0, + "k": 4, + "ix": 13 + }, + "bm": 0, + "nm": "Gradient Stroke 1", + "mn": "ADBE Vector Graphic - G-Stroke", + "hd": false + } + ], + "ip": 0, + "op": 210.000008553475, + "st": -31.0000012626559, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "done", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 205, + "s": [ + 100 + ] + }, + { + "t": 209.000008512745, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 549.483, + 573.493, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 33.965, + 33.965, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -87.63, + -28.839 + ], + [ + -9.979, + 47.039 + ], + [ + 156.105, + -125.833 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.179 + ], + "y": [ + 1.809 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 134, + "s": [ + 0 + ] + }, + { + "t": 151.000006150356, + "s": [ + 100 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 0, + "k": 0, + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "trimpaths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.819607843137, + 0.007843137255, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 14, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "stroke1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -61.579, + -60.134 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": -0.173, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "group 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 120.0000048877, + "op": 210.000008553475, + "st": 120.0000048877, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "circ 2", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 120, + "s": [ + 0 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 129, + "s": [ + 0 + ] + }, + { + "t": 134.000005457932, + "s": [ + 100 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 543.399, + 533.989, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 47.836, + 47.836, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -80.36, + 0 + ], + [ + 0, + -80.36 + ], + [ + 80.36, + 0 + ], + [ + 0, + 80.36 + ] + ], + "o": [ + [ + 80.36, + 0 + ], + [ + 0, + 80.36 + ], + [ + -80.36, + 0 + ], + [ + 0, + -80.36 + ] + ], + "v": [ + [ + 0, + -145.504 + ], + [ + 145.504, + 0 + ], + [ + 0, + 145.504 + ], + [ + -145.504, + 0 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 100, + "ix": 1 + }, + "e": { + "a": 0, + "k": 0, + "ix": 2 + }, + "o": { + "a": 0, + "k": -56, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "trimpaths 2", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -8.496, + 11.504 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "group 2", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 120.0000048877, + "op": 210.000008553475, + "st": 120.0000048877, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "wifi/sdk_elements_02 Outlines 4", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 120, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 125, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 129, + "s": [ + 0 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 205, + "s": [ + 0 + ] + }, + { + "t": 209.000008512745, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 540, + 540, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 70.5, + 70.5, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.9, + -0.301 + ], + [ + -0.6, + -2 + ], + [ + -0.6, + -1.099 + ], + [ + 0, + -4 + ], + [ + 1.9, + -3.1 + ], + [ + -0.4, + -1.199 + ], + [ + -1, + -0.4 + ], + [ + -1.8, + 2.799 + ], + [ + 5.1, + 7.901 + ] + ], + "o": [ + [ + -2.3, + 0.399 + ], + [ + 0.1, + 0.5 + ], + [ + 1.9, + 3.101 + ], + [ + 0, + 4 + ], + [ + -1.5, + 2.4 + ], + [ + 0.3, + 1.101 + ], + [ + 2.1, + 0.899 + ], + [ + 5.1, + -7.901 + ], + [ + -1.8, + -2.699 + ] + ], + "v": [ + [ + -3.1, + -16.549 + ], + [ + -6.4, + -11.65 + ], + [ + -5, + -8.85 + ], + [ + -2.7, + -0.15 + ], + [ + -5, + 8.551 + ], + [ + -6.4, + 13.15 + ], + [ + -4, + 15.951 + ], + [ + 1.9, + 13.051 + ], + [ + 1.9, + -13.35 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.819999964097, + 0.008000000785, + 1 + ], + "ix": 4 + }, + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 60, + "s": [ + 0 + ] + }, + { + "t": 75.0000030548126, + "s": [ + 100 + ] + } + ], + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 43.278, + 71.784 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 2.2, + 0 + ], + [ + 1, + -0.901 + ], + [ + -2.3, + -4.599 + ], + [ + 5.3, + -10.7 + ], + [ + -0.1, + -0.801 + ], + [ + -1.2, + -0.5 + ], + [ + -0.5, + 0.101 + ], + [ + -1.9, + 3.8 + ], + [ + 0, + 7.199 + ], + [ + 3.1, + 6.301 + ] + ], + "o": [ + [ + -1.3, + 0 + ], + [ + -1.9, + 1.799 + ], + [ + 5.2, + 10.5 + ], + [ + -1.4, + 2.901 + ], + [ + 0.3, + 1.3 + ], + [ + 0.5, + 0.2 + ], + [ + 1.9, + -0.299 + ], + [ + 3.3, + -6.7 + ], + [ + 0, + -7 + ], + [ + -1.9, + -4.099 + ] + ], + "v": [ + [ + -2.5, + -24 + ], + [ + -5.2, + -22.9 + ], + [ + -4.8, + -15.601 + ], + [ + -5, + 15.899 + ], + [ + -6.6, + 20.5 + ], + [ + -4.1, + 23.6 + ], + [ + -2.1, + 23.899 + ], + [ + 2.6, + 18.8 + ], + [ + 7.1, + 0 + ], + [ + 2.8, + -18.801 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.819999964097, + 0.008000000785, + 1 + ], + "ix": 4 + }, + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 75, + "s": [ + 0 + ] + }, + { + "t": 90.0000036657751, + "s": [ + 100 + ] + } + ], + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 57.978, + 71.735 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 2", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 4.4, + 13.8 + ], + [ + 1.1, + 1 + ], + [ + 1.3, + -1.7 + ], + [ + -2.2, + -4.599 + ], + [ + -0.7, + -7.5 + ], + [ + 4.2, + -8.601 + ], + [ + 0.2, + -0.5 + ], + [ + -1.9, + -0.601 + ], + [ + -2.9, + 6.9 + ] + ], + "o": [ + [ + -1.4, + -4.2 + ], + [ + -1.6, + -1.5 + ], + [ + -1.3, + 1.899 + ], + [ + 3.4, + 6.801 + ], + [ + 0.9, + 9 + ], + [ + -1, + 2 + ], + [ + -0.6, + 1.8 + ], + [ + 2.9, + 0.899 + ], + [ + 5.9, + -13.6 + ] + ], + "v": [ + [ + 4.5, + -18.6 + ], + [ + -1.5, + -31.1 + ], + [ + -7.5, + -30.6 + ], + [ + -6.5, + -23.4 + ], + [ + -1.1, + -4.4 + ], + [ + -6.2, + 22.4 + ], + [ + -8.3, + 26.9 + ], + [ + -5.5, + 31.701 + ], + [ + 2.4, + 23.501 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.819999964097, + 0.008000000785, + 1 + ], + "ix": 4 + }, + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 90, + "s": [ + 0 + ] + }, + { + "t": 105.000004276738, + "s": [ + 100 + ] + } + ], + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 75.178, + 71.835 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 3", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 3, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0.8, + 5.199 + ], + [ + 3.8, + 7.4 + ], + [ + 0.4, + 0.399 + ], + [ + 1.4, + -0.899 + ], + [ + 0, + -1.1 + ], + [ + -1.2, + -2.3 + ], + [ + 0, + -11.5 + ], + [ + 5.5, + -10.599 + ], + [ + -0.2, + -0.899 + ], + [ + -1.3, + -0.401 + ], + [ + -0.2, + 0 + ], + [ + -0.3, + 0 + ], + [ + -2.2, + 4.7 + ], + [ + -1.2, + 8.8 + ] + ], + "o": [ + [ + -1.3, + -8.4 + ], + [ + -0.9, + -1.901 + ], + [ + -1.1, + -1.101 + ], + [ + -1, + 0.601 + ], + [ + 0, + 0.299 + ], + [ + 5.3, + 10.7 + ], + [ + 0, + 11.901 + ], + [ + -1.4, + 2.601 + ], + [ + 0.3, + 1.5 + ], + [ + 0.6, + 0.2 + ], + [ + 0.2, + -0.101 + ], + [ + 1.2, + 0 + ], + [ + 3.7, + -7.7 + ], + [ + 0.5, + -4.799 + ] + ], + "v": [ + [ + 8.05, + -10.9 + ], + [ + 0.45, + -34.8 + ], + [ + -2.05, + -38.9 + ], + [ + -6.95, + -39.201 + ], + [ + -8.95, + -35.8 + ], + [ + -6.85, + -31.1 + ], + [ + 0.55, + -0.001 + ], + [ + -7.35, + 32.299 + ], + [ + -8.75, + 36.499 + ], + [ + -5.85, + 39.9 + ], + [ + -4.45, + 40.1 + ], + [ + -3.55, + 39.999 + ], + [ + 1.25, + 33.4 + ], + [ + 8.45, + 8.999 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.819999964097, + 0.008000000785, + 1 + ], + "ix": 4 + }, + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 105, + "s": [ + 0 + ] + }, + { + "t": 120.0000048877, + "s": [ + 100 + ] + } + ], + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 91.028, + 71.635 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 4", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 4, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 60.0000024438501, + "op": 210.000008553475, + "st": -31.0000012626559, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "wifi/sdk_elements_02 Outlines 2", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 120, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 125, + "s": [ + 100 + ] + }, + { + "t": 129.000005254278, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 540, + 540, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 70.5, + 70.5, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.9, + -0.301 + ], + [ + -0.6, + -2 + ], + [ + -0.6, + -1.099 + ], + [ + 0, + -4 + ], + [ + 1.9, + -3.1 + ], + [ + -0.4, + -1.199 + ], + [ + -1, + -0.4 + ], + [ + -1.8, + 2.799 + ], + [ + 5.1, + 7.901 + ] + ], + "o": [ + [ + -2.3, + 0.399 + ], + [ + 0.1, + 0.5 + ], + [ + 1.9, + 3.101 + ], + [ + 0, + 4 + ], + [ + -1.5, + 2.4 + ], + [ + 0.3, + 1.101 + ], + [ + 2.1, + 0.899 + ], + [ + 5.1, + -7.901 + ], + [ + -1.8, + -2.699 + ] + ], + "v": [ + [ + -3.1, + -16.549 + ], + [ + -6.4, + -11.65 + ], + [ + -5, + -8.85 + ], + [ + -2.7, + -0.15 + ], + [ + -5, + 8.551 + ], + [ + -6.4, + 13.15 + ], + [ + -4, + 15.951 + ], + [ + 1.9, + 13.051 + ], + [ + 1.9, + -13.35 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.866957720588, + 0.866957720588, + 0.866957720588, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 43.278, + 71.784 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 2.2, + 0 + ], + [ + 1, + -0.901 + ], + [ + -2.3, + -4.599 + ], + [ + 5.3, + -10.7 + ], + [ + -0.1, + -0.801 + ], + [ + -1.2, + -0.5 + ], + [ + -0.5, + 0.101 + ], + [ + -1.9, + 3.8 + ], + [ + 0, + 7.199 + ], + [ + 3.1, + 6.301 + ] + ], + "o": [ + [ + -1.3, + 0 + ], + [ + -1.9, + 1.799 + ], + [ + 5.2, + 10.5 + ], + [ + -1.4, + 2.901 + ], + [ + 0.3, + 1.3 + ], + [ + 0.5, + 0.2 + ], + [ + 1.9, + -0.299 + ], + [ + 3.3, + -6.7 + ], + [ + 0, + -7 + ], + [ + -1.9, + -4.099 + ] + ], + "v": [ + [ + -2.5, + -24 + ], + [ + -5.2, + -22.9 + ], + [ + -4.8, + -15.601 + ], + [ + -5, + 15.899 + ], + [ + -6.6, + 20.5 + ], + [ + -4.1, + 23.6 + ], + [ + -2.1, + 23.899 + ], + [ + 2.6, + 18.8 + ], + [ + 7.1, + 0 + ], + [ + 2.8, + -18.801 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.866957720588, + 0.866957720588, + 0.866957720588, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 57.978, + 71.735 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 2", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 4.4, + 13.8 + ], + [ + 1.1, + 1 + ], + [ + 1.3, + -1.7 + ], + [ + -2.2, + -4.599 + ], + [ + -0.7, + -7.5 + ], + [ + 4.2, + -8.601 + ], + [ + 0.2, + -0.5 + ], + [ + -1.9, + -0.601 + ], + [ + -2.9, + 6.9 + ] + ], + "o": [ + [ + -1.4, + -4.2 + ], + [ + -1.6, + -1.5 + ], + [ + -1.3, + 1.899 + ], + [ + 3.4, + 6.801 + ], + [ + 0.9, + 9 + ], + [ + -1, + 2 + ], + [ + -0.6, + 1.8 + ], + [ + 2.9, + 0.899 + ], + [ + 5.9, + -13.6 + ] + ], + "v": [ + [ + 4.5, + -18.6 + ], + [ + -1.5, + -31.1 + ], + [ + -7.5, + -30.6 + ], + [ + -6.5, + -23.4 + ], + [ + -1.1, + -4.4 + ], + [ + -6.2, + 22.4 + ], + [ + -8.3, + 26.9 + ], + [ + -5.5, + 31.701 + ], + [ + 2.4, + 23.501 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.866957720588, + 0.866957720588, + 0.866957720588, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 75.178, + 71.835 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 3", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 3, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0.8, + 5.199 + ], + [ + 3.8, + 7.4 + ], + [ + 0.4, + 0.399 + ], + [ + 1.4, + -0.899 + ], + [ + 0, + -1.1 + ], + [ + -1.2, + -2.3 + ], + [ + 0, + -11.5 + ], + [ + 5.5, + -10.599 + ], + [ + -0.2, + -0.899 + ], + [ + -1.3, + -0.401 + ], + [ + -0.2, + 0 + ], + [ + -0.3, + 0 + ], + [ + -2.2, + 4.7 + ], + [ + -1.2, + 8.8 + ] + ], + "o": [ + [ + -1.3, + -8.4 + ], + [ + -0.9, + -1.901 + ], + [ + -1.1, + -1.101 + ], + [ + -1, + 0.601 + ], + [ + 0, + 0.299 + ], + [ + 5.3, + 10.7 + ], + [ + 0, + 11.901 + ], + [ + -1.4, + 2.601 + ], + [ + 0.3, + 1.5 + ], + [ + 0.6, + 0.2 + ], + [ + 0.2, + -0.101 + ], + [ + 1.2, + 0 + ], + [ + 3.7, + -7.7 + ], + [ + 0.5, + -4.799 + ] + ], + "v": [ + [ + 8.05, + -10.9 + ], + [ + 0.45, + -34.8 + ], + [ + -2.05, + -38.9 + ], + [ + -6.95, + -39.201 + ], + [ + -8.95, + -35.8 + ], + [ + -6.85, + -31.1 + ], + [ + 0.55, + -0.001 + ], + [ + -7.35, + 32.299 + ], + [ + -8.75, + 36.499 + ], + [ + -5.85, + 39.9 + ], + [ + -4.45, + 40.1 + ], + [ + -3.55, + 39.999 + ], + [ + 1.25, + 33.4 + ], + [ + 8.45, + 8.999 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.866957720588, + 0.866957720588, + 0.866957720588, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 91.028, + 71.635 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 4", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 4, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 210.000008553475, + "st": -31.0000012626559, + "bm": 0 + } + ], + "markers": [] +} \ No newline at end of file diff --git a/lib/domain_layer/entities/generated_private_key.dart b/lib/domain_layer/entities/generated_private_key.dart index 7689b187..80160ccf 100644 --- a/lib/domain_layer/entities/generated_private_key.dart +++ b/lib/domain_layer/entities/generated_private_key.dart @@ -6,8 +6,8 @@ class GeneratedPrivateKey { final String privateKey; final String publicKey; - String get privKeyHr => Helpers().encodeBech32(privateKey, 'nsec'); - String get publicKeyHr => Helpers().encodeBech32(publicKey, 'npub'); + String get privKeyHr => Helpers.encodeBech32(privateKey, 'nsec'); + String get publicKeyHr => Helpers.encodeBech32(publicKey, 'npub'); GeneratedPrivateKey({ required this.mnemonicSentence, diff --git a/lib/domain_layer/usecases/generate_private_key.dart b/lib/domain_layer/usecases/generate_private_key.dart index 8cf71ef2..08f79d20 100644 --- a/lib/domain_layer/usecases/generate_private_key.dart +++ b/lib/domain_layer/usecases/generate_private_key.dart @@ -11,7 +11,7 @@ class GeneratePrivateKey { static GeneratedPrivateKey generateKey() { final mnemonic = Mnemonic.generate( Language.english, - entropyLength: 256, + length: MnemonicLength.words24, ); final sentence = mnemonic.sentence; diff --git a/lib/helpers/bip340.dart b/lib/helpers/bip340.dart index d6a59491..8084f66d 100644 --- a/lib/helpers/bip340.dart +++ b/lib/helpers/bip340.dart @@ -34,8 +34,8 @@ class Bip340 { final privKey = _helpers.getSecureRandomHex(32); final pubKey = getPublicKey(privKey); - final privKeyHr = _helpers.encodeBech32(privKey, 'nsec'); - final pubKeyHr = _helpers.encodeBech32(pubKey, 'npub'); + final privKeyHr = Helpers.encodeBech32(privKey, 'nsec'); + final pubKeyHr = Helpers.encodeBech32(pubKey, 'npub'); return KeyPair( privateKey: privKey, diff --git a/lib/helpers/helpers.dart b/lib/helpers/helpers.dart index 4318cc36..64e88fb8 100644 --- a/lib/helpers/helpers.dart +++ b/lib/helpers/helpers.dart @@ -36,7 +36,7 @@ class Helpers { } /// Encode a hex string + human readable part as a bech32 string - String encodeBech32(String hex, String hrp) { + static String encodeBech32(String hex, String hrp) { final bytes = HEX.decode(hex); final fiveBitWords = _convertBits(bytes, 8, 5, true); return bech32.encode(Bech32(hrp, fiveBitWords), hex.length + hrp.length); @@ -91,7 +91,8 @@ class Helpers { /// [pad] - whether to pad the output if there are not enough bits /// If pad is true, and there are remaining bits after the conversion, then the remaining bits are left-shifted and added to the result /// [return] - the converted data - List _convertBits(List data, int fromBits, int toBits, bool pad) { + static List _convertBits( + List data, int fromBits, int toBits, bool pad) { int acc = 0; int bits = 0; List result = []; @@ -117,7 +118,7 @@ class Helpers { return result; } - String shortHr(String pubkey, {bool cutPubkey = true}) { + static String shortHr(String pubkey, {bool cutPubkey = true}) { final npubHr = encodeBech32(pubkey, "npub"); if (!cutPubkey) { return npubHr; diff --git a/lib/helpers/nevent_helper.dart b/lib/helpers/nevent_helper.dart index dbbd58d3..8bca1d83 100644 --- a/lib/helpers/nevent_helper.dart +++ b/lib/helpers/nevent_helper.dart @@ -15,7 +15,7 @@ class NeventHelper { final List tlvList = _generateTlvList(eventId, authorPubkey, relays); final String dataString = HEX.encode(TlvUtils.encode(tlvList)); - return _helper.encodeBech32(dataString, 'nevent'); + return Helpers.encodeBech32(dataString, 'nevent'); } /// Generates a list of TLV objects diff --git a/lib/helpers/nprofile_helper.dart b/lib/helpers/nprofile_helper.dart index b6ca2021..fc1e9181 100644 --- a/lib/helpers/nprofile_helper.dart +++ b/lib/helpers/nprofile_helper.dart @@ -48,7 +48,7 @@ class NprofileHelper { final Map resultMap = _parseTlvList(tlvList); if (resultMap["pubkey"].length != 64) { - throw Exception("Invalid pubkey length"); + throw Exception("Invalid pubkey length $resultMap"); } return resultMap; @@ -56,7 +56,6 @@ class NprofileHelper { /// expects a map with pubkey and relays and [returns] a bech32 encoded nprofile String mapToBech32(Map map) { - final Helpers helper = Helpers(); final String pubkey = map["pubkey"]; final List relays = List.from(map['relays']); @@ -64,7 +63,7 @@ class NprofileHelper { final Uint8List bytes = TlvUtils.encode(tlvList); final String dataString = HEX.encode(bytes); - return helper.encodeBech32(dataString, "nprofile"); + return Helpers.encodeBech32(dataString, "nprofile"); } Map _parseTlvList(List tlvList) { diff --git a/lib/helpers/wallet_number_formatting.dart b/lib/helpers/wallet_number_formatting.dart new file mode 100644 index 00000000..d3101c0c --- /dev/null +++ b/lib/helpers/wallet_number_formatting.dart @@ -0,0 +1,36 @@ +import 'package:intl/intl.dart'; + +class WalletNumberFormatting { + WalletNumberFormatting._(); + + static const enUSLangCode = "en_US"; + + static String formatSat( + int amount, { + String langCode = enUSLangCode, + }) { + final satFormat = NumberFormat("#,##0.##", langCode); + return satFormat.format(amount); + } + + static String formatFiat( + int amountInCents, { + String langCode = enUSLangCode, + }) { + final fiatFormat = NumberFormat("#,##0.00", langCode); + final amount = amountInCents / 100; + return fiatFormat.format(amount); + } + + static String formatAmount({ + required int amount, + required String unit, + String langCode = enUSLangCode, + }) { + if (unit == "sat") { + return formatSat(amount, langCode: langCode); + } else { + return formatFiat(amount, langCode: langCode); + } + } +} diff --git a/lib/presentation_layer/atoms/copy_to_clipboard.dart b/lib/presentation_layer/atoms/copy_to_clipboard.dart new file mode 100644 index 00000000..e4f31b85 --- /dev/null +++ b/lib/presentation_layer/atoms/copy_to_clipboard.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +class CopyClipboardButton extends StatefulWidget { + final String value; + final String copyText; + final String copyDoneText; + + final Color backgroundColor; + + const CopyClipboardButton({ + super.key, + required this.value, + this.copyText = 'Copy', + this.copyDoneText = 'Copied to Clipboard!', + this.backgroundColor = Colors.white, + }); + + @override + State createState() => _CopyClipboardButtonState(); +} + +class _CopyClipboardButtonState extends State { + bool _copied = false; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: _copied ? null : _copyToClipboard, + icon: + Icon(_copied ? PhosphorIcons.check() : PhosphorIcons.copySimple()), + label: Text(_copied ? widget.copyDoneText : widget.copyText), + style: ElevatedButton.styleFrom( + backgroundColor: _copied + ? widget.backgroundColor + : Theme.of(context).colorScheme.primary, + foregroundColor: widget.backgroundColor, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ); + } + + Future _copyToClipboard() async { + await Clipboard.setData(ClipboardData(text: widget.value)); + setState(() { + _copied = true; + }); + + Future.delayed(const Duration(seconds: 3), () { + if (mounted) { + setState(() { + _copied = false; + }); + } + }); + } +} diff --git a/lib/presentation_layer/atoms/cowntdown_timer.dart b/lib/presentation_layer/atoms/cowntdown_timer.dart new file mode 100644 index 00000000..358def87 --- /dev/null +++ b/lib/presentation_layer/atoms/cowntdown_timer.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +class CountdownTimer extends StatefulWidget { + final int unixTimestamp; + + const CountdownTimer({super.key, required this.unixTimestamp}); + + @override + CountdownTimerState createState() => CountdownTimerState(); +} + +class CountdownTimerState extends State { + Timer? _timer; + int _remainingSeconds = 0; + bool _isExpired = false; + + @override + void initState() { + super.initState(); + _calculateRemainingTime(); + _startTimer(); + } + + void _calculateRemainingTime() { + final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; + _remainingSeconds = widget.unixTimestamp - now; + if (_remainingSeconds <= 0) { + _remainingSeconds = 0; + _isExpired = true; + } + } + + void _startTimer() { + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + setState(() { + if (_remainingSeconds > 0) { + _remainingSeconds--; + } else { + _isExpired = true; + _timer?.cancel(); + } + }); + }); + } + + String _formatTime(int seconds) { + final hours = seconds ~/ 3600; + final minutes = (seconds % 3600) ~/ 60; + final secs = seconds % 60; + return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}'; + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(8), + child: Text( + _isExpired ? 'EXPIRED' : _formatTime(_remainingSeconds), + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: _isExpired ? Colors.red : Colors.black, + ), + ), + ); + } +} diff --git a/lib/presentation_layer/atoms/currency_picker_bar.dart b/lib/presentation_layer/atoms/currency_picker_bar.dart new file mode 100644 index 00000000..de1db39f --- /dev/null +++ b/lib/presentation_layer/atoms/currency_picker_bar.dart @@ -0,0 +1,368 @@ +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../config/palette.dart'; + +class ChangeResult { + final String currentUnit; + final String previousUnit; + + ChangeResult({ + required this.currentUnit, + required this.previousUnit, + }); +} + +class CurrencyPickerBar extends StatefulWidget { + const CurrencyPickerBar({ + super.key, + required this.currencies, + this.initialIndex = 0, + this.onChanged, + this.height = 84, + this.lensRadius = 28, + this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + this.trackColor, + this.activeColor, + this.inactiveColor, + this.showHaptics = true, + this.labelFontSize = 18, + this.snapDuration = const Duration(milliseconds: 180), + this.isDark = true, + }); + + final List currencies; + final int initialIndex; + final ValueChanged? onChanged; + final double height; + final double lensRadius; + final EdgeInsets padding; + final Color? trackColor; + final Color? activeColor; + final Color? inactiveColor; + final bool showHaptics; + final bool isDark; + + final double labelFontSize; + + final Duration snapDuration; + + @override + State createState() => _CurrencyPickerBarState(); +} + +class _CurrencyPickerBarState extends State + with SingleTickerProviderStateMixin { + late int _selectedIndex; + late double _lensX; + late AnimationController _snapController; + Animation? _snapAnim; + + @override + void initState() { + super.initState(); + _selectedIndex = widget.currencies.isEmpty + ? 0 + : widget.initialIndex + .clamp(0, math.max(0, widget.currencies.length - 1)); + _lensX = 0; // will be set on first layout + _snapController = + AnimationController(vsync: this, duration: widget.snapDuration); + } + + @override + void didUpdateWidget(covariant CurrencyPickerBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.snapDuration != widget.snapDuration) { + _snapController.duration = widget.snapDuration; + } + // if (oldWidget.currencies != widget.currencies) { + // widget.onChanged?.call(_selectedIndex); + // } + } + + @override + void dispose() { + _snapController.dispose(); + super.dispose(); + } + + double _leftBound(double width) => widget.padding.left + widget.lensRadius; + double _rightBound(double width) => + width - widget.padding.right - widget.lensRadius; + + // position for index i across the track + double _xForIndex(int i, double width) { + if (widget.currencies.length <= 1) { + return (_leftBound(width) + _rightBound(width)) / 2; + } + final span = _rightBound(width) - _leftBound(width); + final t = i / (widget.currencies.length - 1); + return _leftBound(width) + span * t; + } + + int _nearestIndexForX(double x, double width) { + if (widget.currencies.isEmpty) return 0; + int closest = 0; + double best = double.infinity; + for (int i = 0; i < widget.currencies.length; i++) { + final xi = _xForIndex(i, width); + final d = (x - xi).abs(); + if (d < best) { + best = d; + closest = i; + } + } + return closest; + } + + void _animateLensToIndex(int index, double width) { + final targetX = _xForIndex(index, width); + _snapController.stop(); + final start = _lensX; + _snapAnim = Tween(begin: start, end: targetX) + .chain(CurveTween(curve: Curves.easeOutCubic)) + .animate(_snapController); + _snapAnim!.addListener(() { + setState(() { + _lensX = _snapAnim!.value; + }); + }); + _snapController.forward(from: 0); + } + + void _setSelectedIndex(int index, {bool fromUser = true}) { + final previousIndex = _selectedIndex; + if (index == _selectedIndex) return; + setState(() { + _selectedIndex = index; + }); + if (widget.showHaptics && fromUser) { + HapticFeedback.selectionClick(); + } + + final currentUnit = widget.currencies[_selectedIndex]; + final previousUnit = widget.currencies[previousIndex]; + + widget.onChanged?.call( + ChangeResult( + currentUnit: currentUnit, + previousUnit: previousUnit, + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + final trackColor = widget.trackColor ?? + (widget.isDark + ? Paletter.extraDarkGray.withValues(alpha: 0.22) + : Paletter.lightGray.withValues(alpha: 0.85)); + + final inactiveColor = widget.inactiveColor ?? + (widget.isDark + ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.88) + : Theme.of(context).colorScheme.primary.withValues(alpha: 0.9)); + + final activeColor = widget.activeColor ?? theme.colorScheme.primary; + + return LayoutBuilder( + builder: (context, constraints) { + final width = constraints.maxWidth; + final height = widget.height; + + // initialize lens position on first layout + if (_lensX == 0 && width > 0) { + _lensX = _xForIndex(_selectedIndex, width); + } + + void handleTapOrPanTo(Offset localPos) { + final clamped = + localPos.dx.clamp(_leftBound(width), _rightBound(width)); + setState(() => _lensX = clamped.toDouble()); + final nearest = _nearestIndexForX(_lensX, width); + _setSelectedIndex(nearest); + } + + Widget buildTrackContent() { + final labels = []; + for (int i = 0; i < widget.currencies.length; i++) { + final x = _xForIndex(i, width); + final t = (i == _selectedIndex) ? 1.0 : 0.0; + final color = + Color.lerp(inactiveColor, activeColor, t) ?? activeColor; + + labels.add( + Positioned( + left: x - 36, // centered width=72 + width: 72, + top: 0, + bottom: 0, + child: Center( + child: Text( + widget.currencies[i], + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: theme.textTheme.titleMedium?.copyWith( + fontSize: widget.labelFontSize, + fontWeight: FontWeight.w700, + letterSpacing: 0.3, + color: color, + ), + ), + ), + ), + ); + } + + return Stack( + children: [ + // track background + Positioned.fill( + child: Container( + margin: widget.padding, + decoration: BoxDecoration( + color: trackColor, + borderRadius: BorderRadius.circular(999), + boxShadow: [ + if (!widget.isDark) + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 10, + offset: const Offset(0, 3), + ), + ], + ), + ), + ), + + ...labels, + ], + ); + } + + final lensRadius = widget.lensRadius; + final lensDiameter = lensRadius * 2; + final lensCenterY = height / 2; + final hasItems = widget.currencies.isNotEmpty; + final canDecrease = hasItems && _selectedIndex > 0; + final canIncrease = + hasItems && _selectedIndex < widget.currencies.length - 1; + + return Semantics( + label: 'Currency picker', + value: hasItems ? widget.currencies[_selectedIndex] : '', + increasedValue: + canIncrease ? widget.currencies[_selectedIndex + 1] : null, + decreasedValue: + canDecrease ? widget.currencies[_selectedIndex - 1] : null, + onIncrease: canIncrease + ? () { + _setSelectedIndex(_selectedIndex + 1); + _animateLensToIndex(_selectedIndex, width); + } + : null, + onDecrease: canDecrease + ? () { + _setSelectedIndex(_selectedIndex - 1); + _animateLensToIndex(_selectedIndex, width); + } + : null, + child: SizedBox( + height: height, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTapDown: (d) { + handleTapOrPanTo(d.localPosition); + _animateLensToIndex(_selectedIndex, width); + }, + onHorizontalDragUpdate: (d) { + final moved = (_lensX + d.delta.dx) + .clamp(_leftBound(width), _rightBound(width)); + setState(() => _lensX = moved.toDouble()); + final nearest = _nearestIndexForX(_lensX, width); + _setSelectedIndex(nearest); + }, + onHorizontalDragEnd: (d) { + _animateLensToIndex(_selectedIndex, width); + }, + child: Stack( + clipBehavior: Clip.none, + children: [ + // background track + Positioned.fill(child: buildTrackContent()), + + // lens + Positioned( + left: _lensX - lensRadius, + top: lensCenterY - lensRadius, + width: lensDiameter, + height: lensDiameter, + child: _LensRing( + isDark: widget.isDark, + borderWidth: 3, + shadowColor: widget.isDark + ? Colors.black.withValues(alpha: 0.5) + : Colors.black.withValues(alpha: 0.15), + highlightColor: widget.isDark + ? Colors.white.withValues(alpha: 0.08) + : Colors.white.withValues(alpha: 0.18), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } +} + +class _LensRing extends StatelessWidget { + const _LensRing({ + required this.isDark, + this.borderWidth = 3, + this.shadowColor = Paletter.gray, + this.highlightColor = Paletter.gray, + }); + + final bool isDark; + final double borderWidth; + final Color shadowColor; + final Color highlightColor; + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: borderWidth, + color: isDark + ? Paletter.darkGray + : Theme.of(context).colorScheme.surface, + ), + boxShadow: [ + BoxShadow( + color: Colors.transparent, + blurRadius: 10, + offset: const Offset(0, 4), + ), + BoxShadow( + color: highlightColor, + blurRadius: 8, + spreadRadius: -2, + offset: const Offset(0, -1), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation_layer/atoms/nip_05_text.dart b/lib/presentation_layer/atoms/nip_05_text.dart index c6d9eba1..2d15d0d1 100644 --- a/lib/presentation_layer/atoms/nip_05_text.dart +++ b/lib/presentation_layer/atoms/nip_05_text.dart @@ -8,7 +8,7 @@ class Nip05Text extends StatelessWidget { final bool cutPubkey; String shortHr(String pubkey) { - final npubHr = Helpers().encodeBech32(pubkey, "npub"); + final npubHr = Helpers.encodeBech32(pubkey, "npub"); if (!cutPubkey) { return npubHr; } diff --git a/lib/presentation_layer/atoms/wallet/mint_info_card_small.dart b/lib/presentation_layer/atoms/wallet/mint_info_card_small.dart new file mode 100644 index 00000000..57eac1b4 --- /dev/null +++ b/lib/presentation_layer/atoms/wallet/mint_info_card_small.dart @@ -0,0 +1,305 @@ +import 'package:flutter/material.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import '../../../config/palette.dart'; + +class MintInfoCardSmall extends StatelessWidget { + final ndk_entities.CashuMintInfo mintInfo; + + const MintInfoCardSmall({ + super.key, + required this.mintInfo, + }); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 4, + color: Theme.of(context).colorScheme.surface, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide( + color: Colors.white.withValues(alpha: 0.5), + width: 1, + ), + ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// header + Row( + children: [ + if (mintInfo.iconUrl != null) ...[ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + mintInfo.iconUrl!, + width: 48, + height: 48, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) => + _buildDefaultIcon(), + ), + ), + const SizedBox(width: 12), + ] else + _buildDefaultIcon(), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + mintInfo.name ?? 'Unknown Mint', + style: TextStyle( + color: Colors.white, + fontSize: 22, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (mintInfo.version != null) + Text( + '${mintInfo.version}', + style: TextStyle( + color: Paletter.gray, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 12), + + /// description + if (mintInfo.description != null) ...[ + Text( + mintInfo.description!, + style: TextStyle( + color: Paletter.lightGray, + fontSize: 14, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 24), + ], + + /// motd + if (mintInfo.motd != null) ...[ + Container( + width: double.infinity, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Paletter.gray.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.5), + ), + ), + child: Row( + children: [ + Icon( + PhosphorIcons.megaphone(), + size: 16, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + mintInfo.motd!, + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + const SizedBox(height: 24), + ], + + /// supported units + if (mintInfo.supportedUnits.isNotEmpty) ...[ + Text( + 'Supported Units', + style: TextStyle( + color: Paletter.lightGray, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Wrap( + spacing: 6, + runSpacing: 4, + children: mintInfo.supportedUnits.map((unit) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + child: Text( + unit.toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ); + }).toList(), + ), + const SizedBox(height: 12), + ], + + /// contact info + if (mintInfo.contact.isNotEmpty) ...[ + Text( + 'Contact', + style: TextStyle( + color: Paletter.lightGray, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + ...mintInfo.contact.take(2).map((contact) { + return Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Row( + children: [ + Icon( + PhosphorIcons.userCircle(), + size: 14, + color: Paletter.lightGray, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + contact.info, + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }), + const SizedBox(height: 12), + ], + + /// URLs section + if (mintInfo.urls.isNotEmpty) ...[ + Row( + children: [ + Icon( + PhosphorIcons.link(), + size: 16, + color: Paletter.lightGray, + ), + const SizedBox(width: 4), + Text( + 'URLs', + ), + ], + ), + const SizedBox(height: 4), + ...mintInfo.urls.map((url) { + return Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Text( + url, + style: TextStyle( + color: Paletter.lightGray, + fontSize: 12, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ); + }), + ], + + /// footer + if (mintInfo.time != null || mintInfo.tosUrl != null) ...[ + const SizedBox(height: 8), + const Divider(height: 1), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox.shrink(), + if (mintInfo.tosUrl != null) + TextButton( + onPressed: () async { + launchUrlString(mintInfo.tosUrl!); + }, + child: Row( + children: [ + Icon( + Icons.description_outlined, + size: 12, + color: Paletter.lightGray, + ), + const SizedBox(width: 4), + Text( + 'Terms of Service', + style: TextStyle( + color: Paletter.lightGray, + fontSize: 12, + ), + ), + ], + ), + ) + ], + ), + ], + ], + ), + ), + ), + ); + } + + Widget _buildDefaultIcon() { + return Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: Paletter.darkGray, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + PhosphorIcons.bank(), + size: 24, + color: Paletter.gray, + ), + ); + } +} diff --git a/lib/presentation_layer/atoms/wallet/wallet_card.dart b/lib/presentation_layer/atoms/wallet/wallet_card.dart new file mode 100644 index 00000000..e3c0ca1a --- /dev/null +++ b/lib/presentation_layer/atoms/wallet/wallet_card.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../config/palette.dart'; +import '../../../helpers/wallet_number_formatting.dart'; + +class WalletCard extends StatelessWidget { + final ndk_entities.Wallet wallet; + final List balances; + final Function(String) onTap; + final bool isSelected; + final bool isDisabled; + + final Widget? tralling; + + final bool showBalances; + + final Color backgroundColor; + + const WalletCard({ + super.key, + required this.wallet, + required this.balances, + required this.onTap, + this.isSelected = false, + this.isDisabled = false, + this.backgroundColor = Paletter.extraDarkGray, + this.tralling, + this.showBalances = true, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: isDisabled ? null : () => onTap(wallet.id), + child: Container( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircleAvatar( + backgroundColor: Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.8), + child: Text(wallet.name.substring(0, 2).toUpperCase(), + style: TextStyle(color: Colors.white, fontSize: 12)), + ), + const SizedBox(width: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + wallet.name, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDisabled ? Paletter.gray : Colors.white, + ), + ), + Text( + wallet.id, + style: TextStyle( + fontSize: 14, + color: + isDisabled ? Paletter.gray : Paletter.lightGray, + ), + ), + ], + ), + ], + ), + const SizedBox(width: 12), + Spacer(flex: 1), + if (showBalances) + Column(children: [ + for (final b in balances) + Text( + "${WalletNumberFormatting.formatAmount(amount: b.amount, unit: b.unit)} ${b.unit}", + style: TextStyle( + color: Colors.white, + ), + ), + ]), + if (tralling != null) ...[ + Spacer(flex: 2), + tralling!, + ], + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation_layer/atoms/wallet/wallet_transaction_card.dart b/lib/presentation_layer/atoms/wallet/wallet_transaction_card.dart new file mode 100644 index 00000000..5fee12b4 --- /dev/null +++ b/lib/presentation_layer/atoms/wallet/wallet_transaction_card.dart @@ -0,0 +1,201 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:timeago_flutter/timeago_flutter.dart' as timeago; + +import '../../../config/palette.dart'; +import '../../../helpers/wallet_number_formatting.dart'; + +DateTime _fromUnixSeconds(int seconds) => + DateTime.fromMillisecondsSinceEpoch(seconds * 1000, isUtc: true).toLocal(); + +int? _bestDate(ndk_entities.WalletTransaction tx) => + tx.transactionDate ?? tx.initiatedDate; + +class WalletTransactionCard extends StatelessWidget { + final ndk_entities.WalletTransaction tx; + final bool showDate; + const WalletTransactionCard({ + super.key, + required this.tx, + this.showDate = true, + }); + + @override + Widget build(BuildContext context) { + final amount = tx.changeAmount; + + final transactionStatus = _getTransactionStatus(tx, context); + + final dt = _fromUnixSeconds(_bestDate(tx) ?? 0); + final formattedDate = _formatDate(dt, includeDate: showDate); + + final ndk_entities.CashuWalletTransaction? cashuTx; + if (tx is ndk_entities.CashuWalletTransaction) { + cashuTx = tx as ndk_entities.CashuWalletTransaction; + } else { + cashuTx = null; + } + + return Card( + elevation: 0, + color: Paletter.extraDarkGray.withValues(alpha: 0.75), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide(color: Paletter.darkGray.withValues(alpha: 0.25)), + ), + margin: const EdgeInsets.symmetric(vertical: 6), + child: ListTile( + leading: CircleAvatar( + backgroundColor: transactionStatus.color.withValues(alpha: 0.1), + child: Icon( + transactionStatus.icon, + color: transactionStatus.color, + size: 20, + ), + ), + title: Text( + transactionStatus.label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + subtitle: Text( + "${cashuTx != null ? _removeHttpPrefix(cashuTx.mintUrl) : ""} • ${cashuTx != null ? _txType(cashuTx) : ''}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Paletter.extraLightGray), + ), + trailing: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "${_formatAmount(amount, tx.unit)} ${tx.unit.toUpperCase()}", + style: TextStyle( + color: transactionStatus.color, + fontWeight: FontWeight.w700, + decoration: transactionStatus.isStrikethrough + ? TextDecoration.lineThrough + : null, + ), + ), + const SizedBox(height: 2), + Text( + formattedDate, + style: TextStyle( + color: Paletter.gray, + fontSize: 12, + ), + ), + ], + ), + onTap: () { + context.push('/wallet/transactions/detail', extra: tx); + }, + ), + ); + } + + String _formatDate(DateTime dt, + {bool includeDate = true, int minAgeForDate = 24}) { + if (DateTime.now().difference(dt).inHours < minAgeForDate) { + return timeago.format(dt); + } + + if (includeDate) { + return DateFormat('d.M.yy hh:mm').format(dt); + } else { + return DateFormat('hh:mm').format(dt); + } + } + + String _formatAmount(int amount, String unit) { + final prefix = amount >= 0 ? '+' : ''; + + final formattedAmount = WalletNumberFormatting.formatAmount( + amount: amount, + unit: unit, + ); + return '$prefix$formattedAmount'; + } + + String _removeHttpPrefix(String url) { + return url.replaceFirst(RegExp(r'^https?://'), ''); + } + + String _txType(ndk_entities.CashuWalletTransaction tx) { + if (tx.method == "bolt11") { + return "lightning"; + } + + if (tx.token != null && tx.token!.isNotEmpty) { + return "cashuToken"; + } + + return "unknownType"; + } +} + +class TransactionStatus { + final String label; + final IconData icon; + final Color color; + final bool isStrikethrough; + + const TransactionStatus({ + required this.label, + required this.icon, + required this.color, + required this.isStrikethrough, + }); +} + +TransactionStatus _getTransactionStatus( + ndk_entities.WalletTransaction tx, BuildContext context) { + final isDraft = tx.state == ndk_entities.WalletTransactionState.draft; + final isPending = tx.state == ndk_entities.WalletTransactionState.pending; + final isFailed = tx.state == ndk_entities.WalletTransactionState.failed; + final isCanceled = tx.state == ndk_entities.WalletTransactionState.canceled; + final isIncoming = tx.changeAmount >= 0; + + if (isPending || isDraft) { + return TransactionStatus( + label: 'Pending', + icon: PhosphorIcons.dotsThreeCircle(), + color: Colors.blue, + isStrikethrough: false, + ); + } else if (isFailed) { + return TransactionStatus( + label: 'Failed ${isIncoming ? 'Incoming' : 'Outgoing'}', + icon: PhosphorIcons.warningCircle(), + color: Theme.of(context).colorScheme.error, + isStrikethrough: true, + ); + } else if (isCanceled) { + return TransactionStatus( + label: 'Canceled ${isIncoming ? 'Incoming' : 'Outgoing'}', + icon: PhosphorIcons.xCircle(), + color: Theme.of(context).colorScheme.onError, + isStrikethrough: true, + ); + } else { + // Successful transaction + final color = isIncoming ? Colors.green : Paletter.gray; + final icon = + isIncoming ? PhosphorIcons.arrowDown() : PhosphorIcons.arrowUp(); + + return TransactionStatus( + label: isIncoming ? 'Incoming' : 'Outgoing', + icon: icon, + color: color, + isStrikethrough: false, + ); + } +} diff --git a/lib/presentation_layer/components/drawer/nostr_side_menu.dart b/lib/presentation_layer/components/drawer/nostr_side_menu.dart index a3ea9c2c..2763bdf7 100644 --- a/lib/presentation_layer/components/drawer/nostr_side_menu.dart +++ b/lib/presentation_layer/components/drawer/nostr_side_menu.dart @@ -220,15 +220,10 @@ class NostrSideMenu extends ConsumerWidget { }), _drawerItem( label: AppLocalizations.of(context)!.payments, - routeName: 'payments', icon: PhosphorIcons.lightning(), + routeName: '/wallet/dashboard', onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text(AppLocalizations.of(context)!.notImplementedYet), - ), - ); + context.push('/wallet/dashboard'); }), _drawerItem( label: AppLocalizations.of(context)!.blocklist, diff --git a/lib/presentation_layer/components/note_card/in_reply_to.dart b/lib/presentation_layer/components/note_card/in_reply_to.dart index 821a0e32..f1bc070c 100644 --- a/lib/presentation_layer/components/note_card/in_reply_to.dart +++ b/lib/presentation_layer/components/note_card/in_reply_to.dart @@ -17,7 +17,7 @@ class InReplyTo extends ConsumerWidget { final NostrNote myNote; String _formatPubkey(String pubkey) { - final pubkeyBech = Helpers().encodeBech32(pubkey, "npub"); + final pubkeyBech = Helpers.encodeBech32(pubkey, "npub"); final pubkeyHr = "${pubkeyBech.substring(0, 4)}:${pubkeyBech.substring(pubkeyBech.length - 5)}"; return pubkeyHr; diff --git a/lib/presentation_layer/components/note_card/name_row.dart b/lib/presentation_layer/components/note_card/name_row.dart index 95db4251..7ba23829 100644 --- a/lib/presentation_layer/components/note_card/name_row.dart +++ b/lib/presentation_layer/components/note_card/name_row.dart @@ -49,7 +49,7 @@ class _NoteCardNameRowState extends ConsumerState { } void _initSequence() { - var npubHr = Helpers().encodeBech32(widget.pubkey, "npub"); + var npubHr = Helpers.encodeBech32(widget.pubkey, "npub"); npubHrShort = "${npubHr.substring(0, 4)}...${npubHr.substring(npubHr.length - 4)}"; } diff --git a/lib/presentation_layer/components/note_card/note_card_repost.dart b/lib/presentation_layer/components/note_card/note_card_repost.dart index e504f446..ead942cf 100644 --- a/lib/presentation_layer/components/note_card/note_card_repost.dart +++ b/lib/presentation_layer/components/note_card/note_card_repost.dart @@ -69,7 +69,7 @@ class NoteCardRepost extends ConsumerWidget { children: [ TextSpan( text: repostedByMetadata?.name ?? - Helpers().shortHr(repostEvent.pubkey), + Helpers.shortHr(repostEvent.pubkey), style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15, diff --git a/lib/presentation_layer/components/starter_packs/open_starter_pack.dart b/lib/presentation_layer/components/starter_packs/open_starter_pack.dart index 85a5bc9c..501dddc5 100644 --- a/lib/presentation_layer/components/starter_packs/open_starter_pack.dart +++ b/lib/presentation_layer/components/starter_packs/open_starter_pack.dart @@ -362,7 +362,7 @@ class _OpenStarterPackState extends ConsumerState { ), const SizedBox(height: 4), Text( - "Starter pack by ${isOwnStarterPack ? "you" : ref.watch(metadataStateProvider(widget.starterPackIdentifier.pubkey)).userMetadata?.name ?? Helpers().shortHr(widget.starterPackIdentifier.pubkey)}", + "Starter pack by ${isOwnStarterPack ? "you" : ref.watch(metadataStateProvider(widget.starterPackIdentifier.pubkey)).userMetadata?.name ?? Helpers.shortHr(widget.starterPackIdentifier.pubkey)}", style: TextStyle( color: Paletter.getGray(context), fontSize: 14, @@ -458,8 +458,8 @@ class _OpenStarterPackState extends ConsumerState { return PersonCard( pubkey: displayPubkey, - name: displayMetadata?.name ?? - Helpers().shortHr(displayPubkey), + name: + displayMetadata?.name ?? Helpers.shortHr(displayPubkey), pictureUrl: displayMetadata?.picture ?? "", about: displayMetadata?.about ?? "", isFollowing: myContactListState.contactList.contacts diff --git a/lib/presentation_layer/components/starter_packs/starter_pack_card.dart b/lib/presentation_layer/components/starter_packs/starter_pack_card.dart index 7a7bd718..5825012a 100644 --- a/lib/presentation_layer/components/starter_packs/starter_pack_card.dart +++ b/lib/presentation_layer/components/starter_packs/starter_pack_card.dart @@ -168,7 +168,7 @@ class _StarterPackCardState extends ConsumerState { } _pubkeyToHrBech32Short(pubkey) { - final bech = Helpers().encodeBech32(pubkey, "npub"); + final bech = Helpers.encodeBech32(pubkey, "npub"); final bechShort = NprofileHelper().bech32toHr(bech, cutLength: 11); return bechShort; diff --git a/lib/presentation_layer/components/wallet/animated_qr.dart b/lib/presentation_layer/components/wallet/animated_qr.dart new file mode 100644 index 00000000..dd8418ac --- /dev/null +++ b/lib/presentation_layer/components/wallet/animated_qr.dart @@ -0,0 +1,187 @@ +import 'dart:typed_data'; +import 'dart:convert' show base64Decode, base64Url, utf8; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:bc_ur_dart/bc_ur_dart.dart'; +import 'package:pretty_qr_code/pretty_qr_code.dart'; +import 'dart:async'; + +class AnimatedQr extends ConsumerStatefulWidget { + final String? qrCodeData; + + const AnimatedQr({super.key, this.qrCodeData}); + + @override + ConsumerState createState() => AnimatedQrState(); +} + +class AnimatedQrState extends ConsumerState { + Timer? _timer; + int _currentIndex = 0; + List _urParts = []; + UR? _encoder; + + bool showAnimatedQr = true; + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + _initializeUR(); + } + + @override + void didUpdateWidget(AnimatedQr oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.qrCodeData != widget.qrCodeData) { + _timer?.cancel(); + _initializeUR(); + } + } + + void _initializeUR() { + if (widget.qrCodeData != null && widget.qrCodeData!.isNotEmpty) { + try { + final tokenString = widget.qrCodeData!; + + Uint8List bytes; + if (tokenString.startsWith('cashuB')) { + /// remove cashuB prefix + final tokenData = tokenString.substring(6); + + /// fix: add padding for base64url decoding + String paddedTokenData = tokenData; + while (paddedTokenData.length % 4 != 0) { + paddedTokenData += '='; + } + + bytes = base64Url.decode(paddedTokenData); + } else { + bytes = utf8.encode(tokenString); + } + + const int maxLength = 200; + + _encoder = UR( + payload: bytes, + maxLength: maxLength, + type: "bytes", + ); + + _urParts.clear(); + + final minParts = (bytes.length / maxLength).ceil(); + final targetParts = (minParts * 1.5).ceil(); // 50% overhead + + for (int i = 0; i < targetParts; i++) { + final part = _encoder!.next(); + _urParts.add(part); + } + + if (_urParts.isNotEmpty) { + _startAnimation(); + } + } catch (e) { + // fallback to single QR code if UR encoding fails + _urParts = [widget.qrCodeData!]; + } + } + } + + void _startAnimation() { + if (_urParts.length > 1) { + _timer = Timer.periodic(const Duration(milliseconds: 500), (timer) { + if (mounted) { + setState(() { + _currentIndex = (_currentIndex + 1) % _urParts.length; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + if (_urParts.isEmpty) { + return Container( + width: 200, + height: 200, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: const Center( + child: Text( + 'No QR Data', + style: TextStyle(color: Colors.grey), + ), + ), + ); + } + + return Container( + child: Column( + children: [ + if (showAnimatedQr && _urParts.isNotEmpty) + PrettyQrView.data( + data: _urParts[_currentIndex], + decoration: const PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + color: Colors.black, + ), + background: Colors.white, + ), + ), + const SizedBox(height: 8), + if (!showAnimatedQr) + PrettyQrView.data( + data: widget.qrCodeData ?? '', + decoration: const PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + color: Colors.black, + ), + background: Colors.white, + ), + ), + Row( + children: [ + if (showAnimatedQr && _urParts.length > 1) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.black, width: 1), + ), + child: Text( + '${_currentIndex + 1}/${_urParts.length}', + style: const TextStyle( + color: Colors.black, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ), + const Spacer(), + Switch( + activeColor: Colors.black, + value: showAnimatedQr, + onChanged: (value) { + setState(() { + showAnimatedQr = value; + }); + }), + ], + ) + ], + ), + ); + } +} diff --git a/lib/presentation_layer/components/wallet/payment_history_short.dart b/lib/presentation_layer/components/wallet/payment_history_short.dart new file mode 100644 index 00000000..2c59abd2 --- /dev/null +++ b/lib/presentation_layer/components/wallet/payment_history_short.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:timeago/timeago.dart' as timeago; + +import '../../../config/palette.dart'; +import '../../../helpers/wallet_number_formatting.dart'; +import '../../atoms/wallet/wallet_transaction_card.dart'; + +class PaymentHistoryShort extends StatelessWidget { + final List transactions; + final List pendingTransactions; + final int? maxItems; + final void Function(ndk_entities.WalletTransaction tx)? onTap; + + final String emptyText; + + final ScrollPhysics? physics; + final ScrollController? controller; + + const PaymentHistoryShort({ + super.key, + required this.transactions, + required this.pendingTransactions, + this.maxItems, + this.onTap, + this.emptyText = 'no transactions yet', + this.physics, + this.controller, + }); + + @override + Widget build(BuildContext context) { + if (transactions.isEmpty && pendingTransactions.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 24.0, + ), + child: Center(child: Text(emptyText)), + ); + } + + final sortedPending = + List.from(pendingTransactions); + sortedPending + .sort((a, b) => (_bestDate(b) ?? 0).compareTo(_bestDate(a) ?? 0)); + + final sortedTransactions = + List.from(transactions); + sortedTransactions + .sort((a, b) => (_bestDate(b) ?? 0).compareTo(_bestDate(a) ?? 0)); + + final allTransactions = [...sortedPending, ...sortedTransactions]; + + final visible = maxItems == null + ? allTransactions + : allTransactions.take(maxItems!).toList(); + + final scrollView = CustomScrollView( + controller: controller, + physics: physics, + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final tx = visible[index]; + + return WalletTransactionCard(tx: tx); + }, + childCount: visible.length, + ), + ), + ], + ); + + return Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: scrollView, + ); + } + + static int? _bestDate(ndk_entities.WalletTransaction tx) => + tx.transactionDate ?? tx.initiatedDate; +} + +class TransactionStatus { + final String label; + final IconData icon; + final Color color; + final bool isStrikethrough; + + const TransactionStatus({ + required this.label, + required this.icon, + required this.color, + required this.isStrikethrough, + }); +} diff --git a/lib/presentation_layer/components/wallet/sheet_send_receive.dart b/lib/presentation_layer/components/wallet/sheet_send_receive.dart new file mode 100644 index 00000000..01fcae82 --- /dev/null +++ b/lib/presentation_layer/components/wallet/sheet_send_receive.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; + +class WalletSheetSendReceive extends StatelessWidget { + const WalletSheetSendReceive( + {super.key, + required this.isHideBottomNavBar, + required this.pageChangeStream}); + + final Function(bool) isHideBottomNavBar; + final Stream pageChangeStream; + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + child: Padding( + padding: EdgeInsets.only(top: 25), // const EdgeInsets.all(15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row(children: [ + SizedBox( + height: 85, + child: Column( + children: [ + ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(7), + elevation: 0, + ), + child: + const Icon(Icons.file_upload_outlined, size: 40)), + const SizedBox(height: 5), + const Text("send") + ], + ), + ), + ]), + Row(children: [ + SizedBox( + height: 85, + child: Column( + children: [ + ElevatedButton( + onPressed: () { + print("unimplemented"); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(7), + elevation: 0, + ), + child: + const Icon(Icons.file_download_outlined, size: 40)), + const SizedBox(height: 5), + const Text("receive") + ], + ), + ), + ]), + ], + ), + ), + ); + } +} diff --git a/lib/presentation_layer/components/wallet/wallet_account_card_placeholder.dart b/lib/presentation_layer/components/wallet/wallet_account_card_placeholder.dart new file mode 100644 index 00000000..753e6044 --- /dev/null +++ b/lib/presentation_layer/components/wallet/wallet_account_card_placeholder.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +import '../../../config/palette.dart'; + +class WalletAccountCardPlaceholder extends StatelessWidget { + const WalletAccountCardPlaceholder({ + super.key, + this.onTap, + }); + + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + decoration: BoxDecoration( + color: Paletter.extraDarkGray, + borderRadius: BorderRadius.circular(18.0), + border: Border.all( + width: 1, + color: Paletter.gray, + ), + ), + child: Center( + child: Text( + 'add wallet +', + style: TextStyle( + fontSize: 16, + color: Colors.grey[700], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation_layer/components/wallet/wallet_accounts_card.dart b/lib/presentation_layer/components/wallet/wallet_accounts_card.dart new file mode 100644 index 00000000..220dddf7 --- /dev/null +++ b/lib/presentation_layer/components/wallet/wallet_accounts_card.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lottie/lottie.dart'; +import 'package:intl/intl.dart'; + +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../helpers/wallet_number_formatting.dart'; + +class WalletAccountsCard extends ConsumerWidget { + const WalletAccountsCard({ + super.key, + required this.walletId, + required this.title, + required this.nfcAnimController, + required this.alias, + required this.balances, + }); + + final String walletId; + final String title; + final String alias; + + final AnimationController nfcAnimController; + + /// ₿ + final List balances; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Column( + children: [ + Column( + children: [ + Container( + width: 370, + height: 210, + decoration: BoxDecoration( + // gradient: const RadialGradient( + // colors: [ + // Color.fromARGB(255, 7, 238, 176), + // Color.fromARGB(255, 11, 189, 243), + // ], + // stops: [ + // 0, + // 1, + // ], + // focal: Alignment.center, + // radius: 2, + // ), + border: Border.all( + width: 1, + color: Colors.white, + ), + // Make rounded corners + borderRadius: BorderRadius.circular(18.0), + ), + child: Container( + margin: const EdgeInsets.all(10.0), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + alias, + style: TextStyle(fontSize: 18), + ), + Container( + margin: const EdgeInsets.fromLTRB(10.0, 10, 0, 0), + child: Text( + title, + style: TextStyle( + fontSize: 35, + color: Colors.white, + fontWeight: FontWeight.normal), + ), + ), + // const SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.65, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + for (final balance in balances) + Text( + "${WalletNumberFormatting.formatAmount(amount: balance.amount, unit: balance.unit)} ${balance.unit}", + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.normal, + color: Colors.white60, + ), + ), + ], + ), + ), + ], + ), + //lottie animation + Positioned( + right: -50, + child: Transform( + transform: Matrix4.translationValues( + MediaQuery.of(context).size.width * 0, + -20.0, + -20.0), + child: Lottie.asset( + 'assets/animations/nfc-mood.json', + width: 150, + //fit: BoxFit.cover, + controller: nfcAnimController, + onLoaded: (composition) { + nfcAnimController.duration = composition.duration; + nfcAnimController.repeat(); + nfcAnimController.forward(); + }, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/presentation_layer/components/wallet/wallet_actions_strip.dart b/lib/presentation_layer/components/wallet/wallet_actions_strip.dart new file mode 100644 index 00000000..ed31ce5b --- /dev/null +++ b/lib/presentation_layer/components/wallet/wallet_actions_strip.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../../config/palette.dart'; + +class WalletActionsStrip extends StatelessWidget { + final void Function() onScan; + final void Function() onReceive; + final void Function() onPay; + final void Function() onHistory; + + const WalletActionsStrip({ + super.key, + required this.onScan, + required this.onReceive, + required this.onPay, + required this.onHistory, + }); + + final myIconColor = Colors.white70; + final myTextColor = Colors.white60; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SizedBox( + width: MediaQuery.of(context).size.width, + //height: 120, + //color: Colors.white12, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + padding: const EdgeInsets.all(2), + child: _actionButton( + iconData: PhosphorIcons.barcode(), + onTab: () => onScan(), + text: "scan", + ), + ), + Container( + padding: const EdgeInsets.all(2), + child: _actionButton( + iconData: PhosphorIcons.wallet(), + onTab: () => onPay(), + text: "pay", + ), + ), + Container( + padding: const EdgeInsets.all(2), + child: _actionButton( + iconData: PhosphorIcons.piggyBank(), + onTab: () => onReceive(), + text: "receive"), + ), + Container( + padding: const EdgeInsets.all(2), + child: _actionButton( + iconData: PhosphorIcons.receipt(), + text: "history", + onTab: () => onHistory(), + )), + ], + ), + ), + ); + } +} + +Widget _actionButton({ + required Function onTab, + required IconData iconData, + required String text, + Color? iconColor, + Color? textColor, +}) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () => onTab(), + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + fixedSize: const Size(50, 50), + padding: EdgeInsets.zero, + ), + child: Icon( + iconData, + color: iconColor ?? Paletter.gray, + size: 23, + ), + ), + Text( + text, + style: TextStyle(color: textColor ?? Paletter.gray), + ) + ], + ); +} diff --git a/lib/presentation_layer/components/wallet/wallet_friends_strip.dart b/lib/presentation_layer/components/wallet/wallet_friends_strip.dart new file mode 100644 index 00000000..0e407a93 --- /dev/null +++ b/lib/presentation_layer/components/wallet/wallet_friends_strip.dart @@ -0,0 +1,82 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; + +class WalletFriendsStrip extends StatelessWidget { + const WalletFriendsStrip({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: 130, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(10))), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.fromLTRB(15, 4, 15, 0), + child: Text( + "friends", + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ), + Container( + margin: const EdgeInsets.fromLTRB(15, 2, 15, 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + borderRadius: const BorderRadius.all(Radius.circular(10)), + onTap: () => {print("unimplemented")}, + child: Column( + children: const [ + CircleAvatar( + backgroundImage: NetworkImage( + "https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg"), + radius: 30, + ), + Text( + "user1", + style: TextStyle(fontSize: 15), + ), + ], + ), + ), + const SizedBox(width: 15), + InkWell( + borderRadius: const BorderRadius.all(Radius.circular(10)), + onTap: () => {print("nimplemented;")}, + child: Column( + children: const [ + CircleAvatar( + backgroundImage: NetworkImage( + "https://www.venmond.com/demo/vendroid/img/avatar/big.jpg"), + radius: 30, + ), + Text( + "user2", + style: TextStyle(fontSize: 15), + ), + ], + ), + ), + const SizedBox(width: 15), + InkWell( + borderRadius: const BorderRadius.all(Radius.circular(50)), + onTap: () => {log("todo")}, + child: const CircleAvatar( + backgroundColor: Colors.white60, + radius: 30, + child: Icon(Icons.add), + ), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/presentation_layer/components/wallet/wallets_carousel.dart b/lib/presentation_layer/components/wallet/wallets_carousel.dart new file mode 100644 index 00000000..9336aa4c --- /dev/null +++ b/lib/presentation_layer/components/wallet/wallets_carousel.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'wallet_account_card_placeholder.dart'; +import 'wallet_accounts_card.dart'; + +class WalletsCarousel extends StatefulWidget { + final List wallets; + final List balances; + final AnimationController? nfcAnimController; + + const WalletsCarousel({ + super.key, + required this.wallets, + required this.balances, + this.nfcAnimController, + }); + + @override + State createState() => _WalletsCarouselState(); +} + +class _WalletsCarouselState extends State { + late PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController( + viewportFraction: _viewportForCount(widget.wallets.length), + ); + } + + @override + void didUpdateWidget(covariant WalletsCarousel oldWidget) { + super.didUpdateWidget(oldWidget); + if ((oldWidget.wallets.length > 1) != (widget.wallets.length > 1)) { + _pageController.dispose(); + _pageController = PageController( + viewportFraction: _viewportForCount(widget.wallets.length), + ); + setState(() {}); + } + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + double _viewportForCount(int count) { + // show a peek of the next card + return count <= 1 ? 1.0 : 0.9; + } + + @override + Widget build(BuildContext context) { + final wallets = widget.wallets; + + if (wallets.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SizedBox( + height: 210, + child: _CardMaxWidth( + child: WalletAccountCardPlaceholder( + onTap: () { + context.push('/wallet/add_mint'); + }, + ), + ), + ), + ); + } + + final hasMultiple = wallets.length > 1; + + return SizedBox( + height: 210, + child: PageView.builder( + controller: _pageController, + physics: hasMultiple + ? const BouncingScrollPhysics() + : const NeverScrollableScrollPhysics(), + itemCount: wallets.length, + padEnds: false, + itemBuilder: (context, index) { + final w = wallets[index]; + + final String? mintUrl; + if (w is ndk_entities.CashuWallet) { + mintUrl = w.mintUrl; + } else { + mintUrl = null; + } + + final title = + w.name.isNotEmpty ? w.name : mintUrl ?? 'Wallet ${index + 1}'; + final alias = w.type.toString(); + + final myBalances = widget.balances + .where( + (b) => b.walletId == w.id, + ) + .toList(); + + return Padding( + // keep first left so next card peeks + padding: EdgeInsets.only( + left: index == 0 ? 16 : 8, + right: index == wallets.length - 1 ? 16 : 8, + ), + child: _CardMaxWidth( + child: GestureDetector( + onTap: () { + if (mintUrl != null) { + context.push('/wallet/mint_details', extra: mintUrl); + } + }, + child: WalletAccountsCard( + walletId: w.id, + title: title, + alias: alias, + balances: myBalances, + nfcAnimController: widget.nfcAnimController!), + ), + ), + ); + }, + ), + ); + } +} + +/// prevents shrink layouts +class _CardMaxWidth extends StatelessWidget { + final Widget child; + const _CardMaxWidth({required this.child}); + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: child, + ); + } +} diff --git a/lib/presentation_layer/components/wallet/wallets_select_bottom_sheet.dart b/lib/presentation_layer/components/wallet/wallets_select_bottom_sheet.dart new file mode 100644 index 00000000..48265721 --- /dev/null +++ b/lib/presentation_layer/components/wallet/wallets_select_bottom_sheet.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import '../../../config/palette.dart'; +import '../../atoms/wallet/wallet_card.dart'; + +Future showWalletsSelectBottomSheet({ + required BuildContext context, + required List wallets, + required List balances, + String? title, + String? selectedId, + double maxHeightFactor = 0.7, // cap as a fraction of screen height +}) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Theme.of(context).colorScheme.surface, + builder: (ctx) { + final size = MediaQuery.of(ctx).size; + + const double handleHeight = 8 + 4 + 8; + final double maxScrollableHeight = + (size.height * maxHeightFactor) - handleHeight; + + return SafeArea( + top: false, + child: Container( + // upper bound + constraints: BoxConstraints(maxHeight: size.height * maxHeightFactor), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + boxShadow: [ + BoxShadow( + blurRadius: 16, color: Theme.of(context).colorScheme.surface) + ], + ), + child: Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(height: 8), + if (title != null) + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + title, + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + ), + Flexible( + fit: FlexFit.loose, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: + maxScrollableHeight.clamp(120.0, double.infinity), + ), + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 16), + child: Wrap( + spacing: 12, + runSpacing: 12, + children: wallets.map((wallet) { + final wBallances = balances + .where((b) => b.walletId == wallet.id) + .toList(); + final isSelected = wallet.id == selectedId; + return WalletCard( + wallet: wallet, + balances: wBallances, + isSelected: isSelected, + onTap: (id) => Navigator.of(ctx).pop(id), + tralling: Radio( + activeColor: + Theme.of(context).colorScheme.primary, + value: wallet.id, + groupValue: isSelected ? wallet.id : null, + onChanged: (value) { + if (value != null) { + Navigator.of(ctx).pop(value); + } + }, + ), + ); + }).toList(), + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); +} diff --git a/lib/presentation_layer/components/write_post.dart b/lib/presentation_layer/components/write_post.dart index c90fb258..7d1298e7 100644 --- a/lib/presentation_layer/components/write_post.dart +++ b/lib/presentation_layer/components/write_post.dart @@ -494,7 +494,7 @@ class _TopBar extends ConsumerWidget { }); getPubkeyHrShort(String pubkey) { - final pubkeyHr = Helpers().encodeBech32(pubkey, "npub"); + final pubkeyHr = Helpers.encodeBech32(pubkey, "npub"); final pubkeyHrShort = "${pubkeyHr.substring(0, 5)}...${pubkeyHr.substring(pubkeyHr.length - 5)}"; return pubkeyHrShort; diff --git a/lib/presentation_layer/providers/app_bar_provider/app_bottom_bar_provider.dart b/lib/presentation_layer/providers/app_bar_provider/app_bottom_bar_provider.dart index d85c78c1..ce25483c 100644 --- a/lib/presentation_layer/providers/app_bar_provider/app_bottom_bar_provider.dart +++ b/lib/presentation_layer/providers/app_bar_provider/app_bottom_bar_provider.dart @@ -1,7 +1,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'dart:async'; -enum NavigationTab { home, search, notifications, chat } +enum NavigationTab { + home, + search, + + notifications, + chat, + wallet, +} class NavigationState { final NavigationTab selectedTab; @@ -34,6 +41,7 @@ class NavigationEvents { final _searchTabController = StreamController.broadcast(); final _notificationsTabController = StreamController.broadcast(); final _chatTabController = StreamController.broadcast(); + final _walletTabController = StreamController.broadcast(); // Expose streams for listeners Stream get onHomeTabSelected => _homeTabController.stream; @@ -41,6 +49,7 @@ class NavigationEvents { Stream get onNotificationsTabSelected => _notificationsTabController.stream; Stream get onChatTabSelected => _chatTabController.stream; + Stream get onWalletTabSelected => _walletTabController.stream; // Methods to trigger events void triggerHomeTabEvent() { @@ -59,11 +68,16 @@ class NavigationEvents { _chatTabController.add(null); } + void triggerWalletTabEvent() { + _walletTabController.add(null); + } + void dispose() { _homeTabController.close(); _searchTabController.close(); _notificationsTabController.close(); _chatTabController.close(); + _walletTabController.close(); } } @@ -100,6 +114,9 @@ class NavigationNotifier extends StateNotifier { case NavigationTab.chat: events.triggerChatTabEvent(); break; + case NavigationTab.wallet: + events.triggerWalletTabEvent(); + break; } } diff --git a/lib/presentation_layer/providers/ndk_provider.dart b/lib/presentation_layer/providers/ndk_provider.dart index 3601d4b3..1951a8c7 100644 --- a/lib/presentation_layer/providers/ndk_provider.dart +++ b/lib/presentation_layer/providers/ndk_provider.dart @@ -1,3 +1,4 @@ +import 'package:ndk/domain_layer/entities/cashu/cashu_user_seedphrase.dart'; import 'package:ndk/ndk.dart'; import 'package:riverpod/riverpod.dart'; @@ -12,14 +13,16 @@ final ndkProvider = Provider((ref) { final bloomFilterRef = ref.read(bloomFilterReferenceProvider); final NdkConfig ndkConfig = NdkConfig( - engine: NdkEngine.JIT, - cache: db!, - eventVerifier: eventVerifier, - bootstrapRelays: camelusBootstrapRelays, - logLevel: Logger.logLevels.warning, - defaultBroadcastConsiderDonePercent: 0.2, - eventOutFilters: [bloomFilterRef], - ); + engine: NdkEngine.JIT, + cache: db!, + eventVerifier: eventVerifier, + bootstrapRelays: camelusBootstrapRelays, + logLevel: Logger.logLevels.warning, + defaultBroadcastConsiderDonePercent: 0.2, + eventOutFilters: [bloomFilterRef], + cashuUserSeedphrase: CashuUserSeedphrase( + seedPhrase: + "market grid grocery useless into bag earn dove measure stay elephant bright")); final ndk = Ndk(ndkConfig); return ndk; diff --git a/lib/presentation_layer/routes/nostr/onboarding/onboarding_login.dart b/lib/presentation_layer/routes/nostr/onboarding/onboarding_login.dart index 929e07a4..2dc73cd3 100644 --- a/lib/presentation_layer/routes/nostr/onboarding/onboarding_login.dart +++ b/lib/presentation_layer/routes/nostr/onboarding/onboarding_login.dart @@ -71,7 +71,7 @@ class _OnboardingLoginPageState extends ConsumerState { var privkey = Helpers().decodeBech32(nsec)[0]; var pubkey = Bip340().getPublicKey(privkey); var privKeyHr = nsec; - var publicKeyHr = Helpers().encodeBech32(pubkey, 'npub'); + var publicKeyHr = Helpers.encodeBech32(pubkey, 'npub'); setState(() { myKeys = KeyPair( @@ -102,8 +102,8 @@ class _OnboardingLoginPageState extends ConsumerState { final privkeyHex = HEX.encode(child.privateKey!); var pubkey = Bip340().getPublicKey(privkeyHex); - var privKeyHr = Helpers().encodeBech32(privkeyHex, 'nsec'); - var publicKeyHr = Helpers().encodeBech32(pubkey, 'npub'); + var privKeyHr = Helpers.encodeBech32(privkeyHex, 'nsec'); + var publicKeyHr = Helpers.encodeBech32(pubkey, 'npub'); setState(() { myKeys = KeyPair( diff --git a/lib/presentation_layer/routes/nostr/profile/profile_page_2.dart b/lib/presentation_layer/routes/nostr/profile/profile_page_2.dart index 77ab6298..fb2c5edd 100644 --- a/lib/presentation_layer/routes/nostr/profile/profile_page_2.dart +++ b/lib/presentation_layer/routes/nostr/profile/profile_page_2.dart @@ -298,7 +298,7 @@ class _BuildProfileHeader extends ConsumerWidget { onTap: () { /// copy to clipboard _copyToClipboard( - Helpers().encodeBech32(userMetadata.pubkey, "npub"), + Helpers.encodeBech32(userMetadata.pubkey, "npub"), ); }, child: Nip05Text( @@ -427,7 +427,7 @@ class _FollowButtonState extends ConsumerState<_FollowButton> { } _pubkeyToHrBech32Short(pubkey) { - final bech = Helpers().encodeBech32(pubkey, "npub"); + final bech = Helpers.encodeBech32(pubkey, "npub"); final bechShort = NprofileHelper().bech32toHr(bech, cutLength: 11); return bechShort; diff --git a/lib/presentation_layer/routes/wallet/add_mint/add_mint_page.dart b/lib/presentation_layer/routes/wallet/add_mint/add_mint_page.dart new file mode 100644 index 00000000..50b33202 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/add_mint/add_mint_page.dart @@ -0,0 +1,224 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/ndk.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import '../../../../config/palette.dart'; +import '../../../atoms/long_button.dart'; +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../atoms/wallet/mint_info_card_small.dart'; +import '../../../providers/ndk_provider.dart'; + +class ValidationState { + final String text; + final bool isValidating; + final bool? isValid; + final ndk_entities.CashuMintInfo? mintInfo; + + const ValidationState({ + this.text = '', + this.isValidating = false, + this.isValid, + this.mintInfo, + }); + + ValidationState copyWith({ + String? text, + bool? isValidating, + bool? isValid, + ndk_entities.CashuMintInfo? mintInfo, + bool clearMintInfo = false, + }) { + return ValidationState( + text: text ?? this.text, + isValidating: isValidating ?? this.isValidating, + isValid: isValid ?? this.isValid, + mintInfo: clearMintInfo ? null : (mintInfo ?? this.mintInfo), + ); + } +} + +class AddMintNotifier extends StateNotifier { + final Ndk _ndk; + AddMintNotifier({ + required Ndk ndk, + }) : _ndk = ndk, + super(const ValidationState()); + + Timer? _debounceTimer; + + void updateText(String text) { + state = state.copyWith( + text: text, + isValid: false, + clearMintInfo: true, + ); + + _debounceTimer?.cancel(); + + if (text.isEmpty) { + state = state.copyWith( + isValidating: false, isValid: null, clearMintInfo: true); + return; + } + + _debounceTimer = Timer(const Duration(milliseconds: 500), () { + _validateText(text); + }); + } + + Future _validateText(String text) async { + state = state.copyWith( + isValidating: true, + clearMintInfo: true, + ); + + final isValid = text.length >= 3 && !text.contains(' '); + + try { + final mintInfoNetwork = + await _ndk.cashu.getMintInfoNetwork(mintUrl: buildValidUrl(text)); + + state = state.copyWith( + isValidating: false, + isValid: isValid, + mintInfo: mintInfoNetwork, + ); + } catch (e) { + state = state.copyWith( + isValidating: false, isValid: false, clearMintInfo: true); + debugPrint('Error validating mint: $e'); + return; + } + } + + String buildValidUrl(String urlToCheck) { + final text = urlToCheck.trim(); + if (text.isEmpty) return ''; + + if (!text.startsWith('http://') && !text.startsWith('https://')) { + return 'https://$text'; + } + return text; + } + + @override + void dispose() { + _debounceTimer?.cancel(); + super.dispose(); + } +} + +final addMintProvider = + StateNotifierProvider.autoDispose( + (ref) { + final ndk = ref.watch(ndkProvider); + return AddMintNotifier(ndk: ndk); + }, +); + +class AddMintPage extends ConsumerWidget { + const AddMintPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final validationState = ref.watch(addMintProvider); + final notifier = ref.read(addMintProvider.notifier); + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + appBar: AppBar( + title: const Text('Add Mint'), + backgroundColor: Theme.of(context).colorScheme.background, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Spacer(flex: 10), + + if (validationState.mintInfo != null) + SingleChildScrollView( + child: MintInfoCardSmall( + mintInfo: validationState.mintInfo!, + ), + ), + + const Spacer(flex: 2), + + Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: LinearProgressIndicator( + value: validationState.isValidating ? null : 0.0, + backgroundColor: Theme.of(context).colorScheme.surface, + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.circular(12), + ), + ), + + TextField( + autofocus: true, + onChanged: notifier.updateText, + decoration: InputDecoration( + hintText: 'Enter mint address...', + border: const OutlineInputBorder(), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Paletter.darkGray), + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Paletter.darkGray), + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + suffixIcon: _buildValidationIcon(validationState), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + + //const Spacer(flex: 1), + ], + ), + ), + bottomNavigationBar: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: longButton( + name: "accept terms & add", + onPressed: () { + if (validationState.isValid == true && + validationState.mintInfo != null) { + final validUrl = notifier.buildValidUrl(validationState.text); + ref.read(ndkProvider).cashu.addMintToKnownMints( + mintUrl: validUrl, + ); + Navigator.pop(context); + } + }, + inverted: true, + disabled: validationState.isValid != true, + ), + ), + ), + ); + } + + Widget? _buildValidationIcon(ValidationState state) { + if (state.text.isEmpty) { + return null; + } + + if (state.isValid == true) { + return Icon( + PhosphorIcons.checkCircle(), + color: Colors.green, + ); + } + + return null; + } +} diff --git a/lib/presentation_layer/routes/wallet/mint_info/mint_info_page.dart b/lib/presentation_layer/routes/wallet/mint_info/mint_info_page.dart new file mode 100644 index 00000000..6f23021d --- /dev/null +++ b/lib/presentation_layer/routes/wallet/mint_info/mint_info_page.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart'; + +import '../../../../config/palette.dart'; +import '../../../atoms/wallet/mint_info_card_small.dart'; +import '../wallet_providers/wallet_combined_state_provider.dart'; + +class MintInfoPage extends ConsumerWidget { + final String? mintUrl; + + const MintInfoPage({ + super.key, + this.mintUrl, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final walletCombined = ref.watch(walletCombinedProvider); + + final mintInfoFilter = walletCombined.wallets.where((wallet) { + return wallet is CashuWallet && wallet.mintUrl == mintUrl; + }); + + if (mintInfoFilter.isEmpty) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + title: Text('Mint Info'), + ), + body: Center( + child: Text('Mint not found'), + ), + ); + } + + final myWallet = mintInfoFilter.first as CashuWallet; + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + title: Text('Mint Info'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + child: MintInfoCardSmall( + mintInfo: myWallet.mintInfo, + ), + ), + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_dashboard.dart b/lib/presentation_layer/routes/wallet/wallet_dashboard.dart new file mode 100644 index 00000000..ee5bebfd --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_dashboard.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../../config/palette.dart'; +import '../../atoms/my_profile_picture.dart'; +import '../../components/drawer/nostr_drawer.dart'; +import '../../components/wallet/payment_history_short.dart'; +import '../../components/wallet/wallet_actions_strip.dart'; +import '../../components/wallet/wallet_friends_strip.dart'; +import '../../components/wallet/wallets_carousel.dart'; +import '../../components/wallet/wallets_select_bottom_sheet.dart'; +import '../../providers/metadata_state_provider.dart'; +import '../../providers/ndk_provider.dart'; +import 'wallet_navigation.dart'; +import 'wallet_pay/wallet_pay_state_provider.dart'; +import 'wallet_providers/wallet_combined_state_provider.dart'; +import 'wallet_receive/wallet_receive_state_provider.dart'; + +class WalletDashboard extends ConsumerStatefulWidget { + const WalletDashboard({super.key}); + + @override + ConsumerState createState() => _WalletDashboardState(); +} + +class _WalletDashboardState extends ConsumerState + with TickerProviderStateMixin { + late AnimationController _nfcAnimController; + + @override + void initState() { + super.initState(); + _nfcAnimController = AnimationController(vsync: this); + + // If using Option 1, initialize the provider + // ref.read(nfcAnimationControllerProvider.notifier).initialize(this); + } + + @override + void dispose() { + _nfcAnimController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final myUserPubkey = ref.read(ndkProvider).accounts.getPublicKey()!; + + final combinedWallet = ref.watch(walletCombinedProvider); + final combinedWalletNotifier = ref.watch(walletCombinedProvider.notifier); + + final wallets = combinedWallet.wallets; + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + surfaceTintColor: Theme.of(context).colorScheme.surface, + leading: Builder( + builder: (context) { + final myMetadata = + ref.watch(metadataStateProvider(myUserPubkey)).userMetadata; + return InkWell( + borderRadius: BorderRadius.circular(100), + onTap: () => Scaffold.of(context).openDrawer(), + child: Padding( + padding: const EdgeInsets.all(9.0), + child: UserImage( + imageUrl: myMetadata?.picture, + pubkey: myUserPubkey, + ), + ), + ); + }, + ), + actions: [ + IconButton( + icon: Icon( + PhosphorIcons.plusCircle(), + size: 30, + ), + onPressed: () { + context.push('/wallet/add_mint'); + }, + ), + ], + ), + backgroundColor: Theme.of(context).colorScheme.surface, + drawer: NostrDrawer(pubkey: myUserPubkey), + body: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(height: 20), + WalletsCarousel( + wallets: wallets, + balances: combinedWallet.balances, + nfcAnimController: _nfcAnimController, + ), + const SizedBox(height: 30), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: WalletFriendsStrip(), + ), + const SizedBox(height: 30), + WalletActionsStrip( + onScan: () { + final navigationNoti = + ref.read(walletNavigationProvider.notifier); + navigationNoti.changeDashboardPage(0); + }, + onReceive: () async { + final selectedId = await showWalletsSelectBottomSheet( + context: context, + wallets: combinedWallet.wallets, + balances: combinedWallet.balances, + title: "Select Wallet to Receive To", + ); + if (selectedId != null) { + final rcvNotifier = ref.read(walletRecieverProvider.notifier); + rcvNotifier.reset(); + rcvNotifier.updateRecieveToWalletId(selectedId); + if (mounted) { + context.push('/wallet/receive'); + } + } + }, + onPay: () async { + final selectedId = await showWalletsSelectBottomSheet( + context: context, + wallets: combinedWallet.wallets, + balances: combinedWallet.balances, + title: "Select Wallet to Pay From", + ); + if (selectedId != null) { + final payNotifier = ref.read(walletPayStateProvider.notifier); + payNotifier.reset(); + payNotifier.updatePayFromWalletId(selectedId); + if (mounted) { + context.push('/wallet/pay'); + } + } + }, + onHistory: () { + final navigationNoti = + ref.read(walletNavigationProvider.notifier); + navigationNoti.changeMainPage(1); + }, + ), + Expanded( + child: PaymentHistoryShort( + transactions: combinedWallet.recentTransactions, + pendingTransactions: combinedWallet.pendingTransactions, + onTap: (tx) { + context.push('/wallet/transactions/detail', extra: tx); + }, + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_navigation.dart b/lib/presentation_layer/routes/wallet/wallet_navigation.dart new file mode 100644 index 00000000..3e721ccf --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_navigation.dart @@ -0,0 +1,288 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../components/wallet/sheet_send_receive.dart'; +import 'wallet_dashboard.dart'; + +import 'wallet_qr_scan.dart'; +import 'wallet_transaction/wallet_transaction_list_page.dart'; + +// Navigation state class +class WalletNavigationState { + final int selectedIndex; + final int dashboardIndex; + final PageController mainPageController; + final PageController dashboardPageController; + final StreamController pageChangeController; + + const WalletNavigationState({ + required this.selectedIndex, + required this.dashboardIndex, + required this.mainPageController, + required this.dashboardPageController, + required this.pageChangeController, + }); + + WalletNavigationState copyWith({ + int? selectedIndex, + int? dashboardIndex, + PageController? mainPageController, + PageController? dashboardPageController, + StreamController? pageChangeController, + }) { + return WalletNavigationState( + selectedIndex: selectedIndex ?? this.selectedIndex, + dashboardIndex: dashboardIndex ?? this.dashboardIndex, + mainPageController: mainPageController ?? this.mainPageController, + dashboardPageController: + dashboardPageController ?? this.dashboardPageController, + pageChangeController: pageChangeController ?? this.pageChangeController, + ); + } +} + +// StateNotifier for managing navigation +class WalletNavigationNotifier extends StateNotifier { + WalletNavigationNotifier() + : super(WalletNavigationState( + selectedIndex: 0, + dashboardIndex: + 1, // Start at dashboard (index 1 in vertical PageView) + mainPageController: PageController(initialPage: 0), + dashboardPageController: PageController(initialPage: 1), + pageChangeController: StreamController.broadcast(), + )); + + // Route mappings + final Map _routeToIndex = { + '/wallet/qr-scan': 0, + '/wallet/dashboard': 0, + '/wallet/receive': 1, + '/wallet/mints': 2, + }; + + final Map _indexToRoute = { + 0: '/wallet/dashboard', + 1: '/wallet/receive', + 2: '/wallet/mints', + }; + + // Change main page (horizontal navigation) + void changeMainPage(int index, + {bool animate = true, bool updateRoute = true}) { + if (index == state.selectedIndex) return; + + state = state.copyWith(selectedIndex: index); + state.pageChangeController.add(index); + + if (animate) { + state.mainPageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } else { + state.mainPageController.jumpToPage(index); + } + } + + // Change dashboard page (vertical navigation) + void changeDashboardPage(int index, {bool animate = true}) { + if (index == state.dashboardIndex) return; + + state = state.copyWith(dashboardIndex: index); + + if (animate) { + state.dashboardPageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } else { + state.dashboardPageController.jumpToPage(index); + } + } + + // Navigate by route name + void navigateToRoute(String route, BuildContext context) { + final index = _routeToIndex[route]; + if (index != null) { + if (route == '/wallet/qr-scan') { + // Special case: navigate to QR scan (dashboard index 0) + changeMainPage(0, updateRoute: false); + changeDashboardPage(0); + } else { + changeMainPage(index); + if (index == 0) { + // Ensure we're on dashboard when navigating to main dashboard + changeDashboardPage(1); + } + } + Navigator.pushReplacementNamed(context, route); + } + } + + // Handle page swipe from PageView + void onMainPageSwipe(int index) { + state = state.copyWith(selectedIndex: index); + state.pageChangeController.add(index); + } + + // Handle dashboard page swipe + void onDashboardPageSwipe(int index) { + state = state.copyWith(dashboardIndex: index); + } + + // Get current route + String get currentRoute { + if (state.selectedIndex == 0 && state.dashboardIndex == 0) { + return '/wallet/qr-scan'; + } + return _indexToRoute[state.selectedIndex] ?? '/wallet/dashboard'; + } + + @override + void dispose() { + state.mainPageController.dispose(); + state.dashboardPageController.dispose(); + state.pageChangeController.close(); + super.dispose(); + } +} + +// Provider +final walletNavigationProvider = + StateNotifierProvider( + (ref) { + return WalletNavigationNotifier(); +}); + +// Convenience providers for easy access +final selectedIndexProvider = Provider((ref) { + return ref.watch(walletNavigationProvider).selectedIndex; +}); + +final dashboardIndexProvider = Provider((ref) { + return ref.watch(walletNavigationProvider).dashboardIndex; +}); + +final mainPageControllerProvider = Provider((ref) { + return ref.watch(walletNavigationProvider).mainPageController; +}); + +final dashboardPageControllerProvider = Provider((ref) { + return ref.watch(walletNavigationProvider).dashboardPageController; +}); + +final pageChangeStreamProvider = Provider>((ref) { + return ref + .watch(walletNavigationProvider) + .pageChangeController + .stream + .asBroadcastStream(); +}); + +class WalletNavigation extends ConsumerStatefulWidget { + const WalletNavigation({super.key, required this.title}); + + final String title; + + @override + ConsumerState createState() => _WalletNavigationState(); +} + +class _WalletNavigationState extends ConsumerState + with SingleTickerProviderStateMixin { + late AnimationController animationController; + + void _onItemLongPress(BuildContext myContext) { + final navigationState = ref.read(walletNavigationProvider); + final currentIndex = navigationState.selectedIndex; + + showModalBottomSheet( + backgroundColor: Colors.black, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + context: myContext, + builder: (context) => WalletSheetSendReceive( + isHideBottomNavBar: (isHideBottomNavBar) { + isHideBottomNavBar + ? animationController.forward() + : animationController.reverse(); + }, + pageChangeStream: ref.read(pageChangeStreamProvider), + ), + ).then((_) { + // Restore previous state + ref.read(walletNavigationProvider.notifier).changeMainPage(currentIndex); + }); + } + + void _onItemTapped(int index, BuildContext myContext) { + ref.read(walletNavigationProvider.notifier).changeMainPage(index); + } + + void _onMainPageSwipe(int index) { + ref.read(walletNavigationProvider.notifier).onMainPageSwipe(index); + } + + void _onDashboardPageSwipe(int index) { + ref.read(walletNavigationProvider.notifier).onDashboardPageSwipe(index); + } + + @override + void initState() { + super.initState(); + animationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 200)); + animationController.forward(); + + // current route from navigator + WidgetsBinding.instance.addPostFrameCallback((_) { + final currentRoute = ModalRoute.of(context)?.settings.name; + if (currentRoute != null) { + ref + .read(walletNavigationProvider.notifier) + .navigateToRoute(currentRoute, context); + } + }); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final navigationState = ref.watch(walletNavigationProvider); + final selectedIndex = ref.watch(selectedIndexProvider); + final mainPageController = ref.watch(mainPageControllerProvider); + final dashboardPageController = ref.watch(dashboardPageControllerProvider); + + return PageView( + scrollDirection: Axis.horizontal, + controller: mainPageController, + onPageChanged: _onMainPageSwipe, + physics: const BouncingScrollPhysics(), + children: [ + PageView( + scrollDirection: Axis.vertical, + controller: dashboardPageController, + onPageChanged: _onDashboardPageSwipe, + children: [ + WalletQrScan(), + WalletDashboard(), + ], + ), + WalletTransactionListPage(), + ], + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_done/wallet_pay_done.dart b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_done/wallet_pay_done.dart new file mode 100644 index 00000000..66874f3e --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_done/wallet_pay_done.dart @@ -0,0 +1,281 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../../../../config/palette.dart'; +import '../../../../../helpers/wallet_number_formatting.dart'; +import '../../../../atoms/copy_to_clipboard.dart'; +import '../../../../atoms/long_button.dart'; +import '../../../../components/wallet/animated_qr.dart'; +import '../../wallet_providers/wallet_combined_state_provider.dart'; +import '../wallet_pay_state_provider.dart'; + +class WalletPayDone extends ConsumerWidget { + const WalletPayDone({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final walletPayState = ref.watch(walletPayStateProvider); + final payNotifier = ref.watch(walletPayStateProvider.notifier); + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: null, + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: _buildCurrentStep(walletPayState), + ), + ), + bottomNavigationBar: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: longButton( + name: "close", + onPressed: () { + payNotifier.reset(); + + context.pop(); + }, + inverted: true), + ), + ), + ); + } + + Widget _buildCurrentStep(WalletPayState state) { + if (state.isProcessing) { + return const ProcessingStep(); + } + + if (state.isError) { + return ErrorStep(errorMessage: state.errorMessage); + } + + if (state.isSuccess && state.outputToken != null) { + return SuccessStep( + outputToken: state.outputToken!, + amount: state.amount!, + unit: state.unit!, + ); + } + + return Container(); + } +} + +// Processing step widget +class ProcessingStep extends StatelessWidget { + const ProcessingStep({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator( + strokeWidth: 3, + ), + SizedBox(height: 24), + Text( + 'Creating Token...', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } +} + +class ErrorStep extends StatelessWidget { + final String? errorMessage; + + const ErrorStep({super.key, this.errorMessage}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + color: Theme.of(context).colorScheme.error, + size: 80, + ), + const SizedBox(height: 24), + Text( + 'Error', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.error, + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.red.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.red.shade200), + ), + child: Text( + errorMessage ?? 'An unknown error occurred', + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onError, + ), + textAlign: TextAlign.center, + ), + ), + ], + ); + } +} + +// Success step widget +class SuccessStep extends StatelessWidget { + final ndk_entities.CashuToken? outputToken; + final int amount; + final String unit; + + const SuccessStep({ + super.key, + required this.outputToken, + required this.amount, + required this.unit, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + WalletNumberFormatting.formatAmount( + amount: amount, + unit: unit, + ), + style: TextStyle( + fontSize: 48, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(width: 16), + Text( + unit, + style: TextStyle( + fontSize: 48, + fontWeight: FontWeight.bold, + color: Paletter.extraLightGray, + ), + ), + ], + ), + const SizedBox(height: 24), + + TransactionState(), + const SizedBox(height: 32), + + Container( + width: 240, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: AnimatedQr( + qrCodeData: outputToken!.toV4TokenString(), + ), + ), + const SizedBox(height: 32), + + const SizedBox(height: 24), + + /// copy button + SizedBox( + width: 250, + child: CopyClipboardButton( + value: outputToken!.toV4TokenString(), + copyText: "Copy Token", + ), + ), + ], + ); + } +} + +class TransactionState extends ConsumerWidget { + const TransactionState({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final walletPayState = ref.watch(walletPayStateProvider); + final walletCombined = ref.watch(walletCombinedProvider); + + final myTransactionList = walletCombined.recentTransactions.where( + (transaction) => transaction.id == walletPayState.transactionId, + ); + + final myPendingTransactionList = walletCombined.pendingTransactions.where( + (transaction) => transaction.id == walletPayState.transactionId, + ); + + if (myTransactionList.isEmpty && myPendingTransactionList.isEmpty) { + return const SizedBox.shrink(); + } + final myTransaction = myTransactionList.isNotEmpty + ? myTransactionList.first + : myPendingTransactionList.first; + + IconData icon; + Color color; + String title; + + if (myTransaction.state == ndk_entities.WalletTransactionState.pending) { + icon = PhosphorIcons.hourglass(); + color = Paletter.lightGray; + title = 'pending ecash'; + } else if (myTransaction.state == + ndk_entities.WalletTransactionState.failed) { + icon = PhosphorIcons.warningCircle(); + color = Theme.of(context).colorScheme.surface; + title = 'failed'; + } else { + icon = PhosphorIcons.checkCircle(); + color = Colors.green; + title = 'ecash claimed'; + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Icon(icon, color: color, size: 23), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: color, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_page.dart b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_page.dart new file mode 100644 index 00000000..6e0fac67 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_page.dart @@ -0,0 +1,74 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'wallet_pay_select_amount/wallet_pay_select_amount.dart'; +import 'wallet_pay_select_reciever/wallet_pay_reciever.dart'; +import 'wallet_pay_summary/wallet_pay_summary.dart'; + +class WalletPayPage extends ConsumerStatefulWidget { + const WalletPayPage({ + super.key, + }); + + @override + ConsumerState createState() => _WalletPayPageState(); +} + +class _WalletPayPageState extends ConsumerState + with TickerProviderStateMixin { + final PageController _horizontalPageController = PageController( + initialPage: 0, + keepPage: true, + ); + + bool horizontalScrollLock = false; + + _navigateToNextPage() { + if (_horizontalPageController.page != null) { + _horizontalPageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + } + + _navigateToPreviousPage() { + if (_horizontalPageController.page != null) { + _horizontalPageController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + } + + @override + Widget build(BuildContext context) { + return PageView( + controller: _horizontalPageController, + physics: horizontalScrollLock + ? const NeverScrollableScrollPhysics() + : const AlwaysScrollableScrollPhysics(), + children: [ + WalletSelectReciever( + doneCallback: () { + _navigateToNextPage(); + }, + ), + WalletPaySelectAmount( + backCallback: () { + _navigateToPreviousPage(); + }, + doneCallback: () { + _navigateToNextPage(); + }, + title: 'Enter amount', + ), + WalletPaySummary( + backCallback: () { + _navigateToPreviousPage(); + }, + ), + ], + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_amount/wallet_pay_select_amount.dart b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_amount/wallet_pay_select_amount.dart new file mode 100644 index 00000000..3749805c --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_amount/wallet_pay_select_amount.dart @@ -0,0 +1,330 @@ +import 'package:camelus/presentation_layer/atoms/wallet/wallet_card.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../../../../config/palette.dart'; +import '../../../../atoms/currency_picker_bar.dart'; +import '../../../../atoms/long_button.dart'; +import '../../../../components/wallet/wallets_select_bottom_sheet.dart'; +import '../wallet_pay_state_provider.dart'; + +class WalletPaySelectAmount extends ConsumerStatefulWidget { + final Function doneCallback; + final Function backCallback; + + const WalletPaySelectAmount({ + super.key, + required this.doneCallback, + required this.backCallback, + this.title, + }); + + final String? title; + + @override + ConsumerState createState() => + _WalletPaySelectAmountState(); +} + +class _WalletPaySelectAmountState extends ConsumerState { + late final TextEditingController _amountController; + late final TextEditingController _memoController; + final FocusNode _amountFocus = FocusNode(); + final FocusNode _memoFocus = FocusNode(); + + @override + void initState() { + super.initState(); + final state = ref.read(walletPayStateProvider); + _amountController = TextEditingController(text: state.amount?.toString()); + _memoController = TextEditingController(text: state.memo); + + _amountController.addListener(() { + final stateR = ref.read(walletPayStateProvider); + if (_amountController.text.isEmpty) { + return; + } + + final int? parsedAmount; + if (stateR.unit == 'sat') { + parsedAmount = int.tryParse(_amountController.text); + } else { + // Handle decimal input for fiat currencies + final sanitizedInput = _amountController.text.replaceAll(',', '.'); + parsedAmount = ((double.tryParse(sanitizedInput) ?? 0) * 100).toInt(); + } + + ref.read(walletPayStateProvider.notifier).updateAmount(parsedAmount ?? 0); + }); + _memoController.addListener(() { + ref + .read(walletPayStateProvider.notifier) + .updateMemo(_memoController.text); + }); + } + + @override + void dispose() { + _amountController.dispose(); + _memoController.dispose(); + _amountFocus.dispose(); + _memoFocus.dispose(); + super.dispose(); + } + + _onSwitchCurrency({ + required String previousUnit, + required String currentUnit, + }) { + final state = ref.read(walletPayStateProvider); + final stateNotifier = ref.read(walletPayStateProvider.notifier); + if (previousUnit == "sat" && currentUnit != "sat") { + /// sat to fiat + final amount = state.amount; + if (amount != null) { + final convertedAmount = (amount).toStringAsFixed(2); + _amountController.text = convertedAmount; + stateNotifier.updateAmount((amount * 100).toInt()); + } + } else if (previousUnit != "sat" && currentUnit == "sat") { + /// fiat to sat + final amount = state.amount; + if (amount != null) { + final convertedAmount = (amount / 100).round(); + _amountController.text = convertedAmount.toString(); + stateNotifier.updateAmount(convertedAmount); + } + } else { + /// fiat to fiat + } + } + + showSnackBar(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: Theme.of(context).colorScheme.onError, + content: Text( + message, + style: TextStyle(color: Colors.white), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final state = ref.watch(walletPayStateProvider); + final notifier = ref.read(walletPayStateProvider.notifier); + + final payState = ref.watch(walletPayStateProvider); + final payNotifier = ref.read(walletPayStateProvider.notifier); + + return Scaffold( + appBar: widget.title != null + ? AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + title: Text(widget.title!), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + widget.backCallback(); + }, + ), + ) + : null, + backgroundColor: Theme.of(context).colorScheme.surface, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 24, 16, 16), + child: Column( + children: [ + Container( + child: WalletCard( + wallet: payState.availableWallets.firstWhere( + (w) => w.id == payState.payFromWalletId, + orElse: () => ndk_entities.CashuWallet( + id: '', + name: 'Select Wallet', + type: ndk_entities.WalletType.CASHU, + supportedUnits: {}, + mintUrl: '', + mintInfo: ndk_entities.CashuMintInfo(nuts: {}), + ), + ), + balances: payState.availableBalances + .where((b) => b.walletId == payState.payFromWalletId) + .toList(), + onTap: (_) {}, + tralling: IconButton( + icon: Icon( + PhosphorIcons.notePencil(), + size: 25, + ), + color: Theme.of(context).colorScheme.primary, + onPressed: () async { + final selectedId = await showWalletsSelectBottomSheet( + context: context, + selectedId: payState.payFromWalletId, + wallets: payState.availableWallets, + balances: payState.availableBalances, + ); + if (selectedId != null) { + payNotifier.updatePayFromWalletId(selectedId); + } + }, + ), + ), + ), + + Spacer(flex: 1), + TextField( + controller: _amountController, + focusNode: _amountFocus, + autofocus: true, + keyboardType: TextInputType.numberWithOptions( + decimal: state.unit != 'sat'), + inputFormatters: state.unit == 'sat' + ? [ + FilteringTextInputFormatter.digitsOnly, + ] + : [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9\.,]')), + DecimalTextInputFormatter(decimalRange: 2), + ], + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.bold, + ), + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.unit != 'sat' ? '0.00' : '0', + ), + ), + + const SizedBox(height: 12), + Spacer(flex: 1), + + /// currency selector + Align( + alignment: Alignment.center, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 320), + child: CurrencyPickerBar( + currencies: state.supportedUnitsByWallet != null + ? state.supportedUnitsByWallet!.toList() + : [], + initialIndex: state.unit != null + ? state.supportedUnitsByWallet! + .toList() + .indexOf(state.unit!) + : 0, + onChanged: (r) => { + payNotifier.updateUnit( + r.currentUnit, + ), + _onSwitchCurrency( + previousUnit: r.previousUnit, + currentUnit: r.currentUnit, + ), + }, + showHaptics: true, + trackColor: Paletter.extraDarkGray, + activeColor: Theme.of(context).colorScheme.primary, + ), + ), + ), + + Spacer(flex: 5), + + /// memo input + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), + child: TextField( + controller: _memoController, + focusNode: _memoFocus, + keyboardType: TextInputType.text, + maxLines: 3, + minLines: 1, + decoration: const InputDecoration( + labelText: 'Memo', + hintText: 'Add a memo (optional)', + border: OutlineInputBorder(), + ), + ), + ), + Spacer(flex: 1), + ], + ), + ), + ), + bottomNavigationBar: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: longButton( + name: "next", + onPressed: () { + if (state.payFromWalletId == null || + state.payFromWalletId!.isEmpty) { + showSnackBar(context, 'Please select a wallet to pay from.'); + return; + } + if (state.amount == null || state.amount! <= 0) { + _amountFocus.requestFocus(); + showSnackBar(context, 'Please enter a valid amount.'); + return; + } + widget.doneCallback(); + }, + inverted: true), + ), + ), + ); + } +} + +class DecimalTextInputFormatter extends TextInputFormatter { + DecimalTextInputFormatter({required this.decimalRange}) + : assert(decimalRange > 0); + + final int decimalRange; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + String newText = newValue.text; + + newText = newText.replaceAll(',', '.'); + + if (newText.isEmpty) { + return newValue.copyWith(text: ''); + } + + if (newText == '.') { + return newValue.copyWith(text: '0.'); + } + + if (!RegExp(r'^\d*\.?\d*').hasMatch(newText)) { + return oldValue; + } + + if (newText.contains('.')) { + List parts = newText.split('.'); + if (parts.length > 2) { + return oldValue; + } + if (parts[1].length > decimalRange) { + // too many decimal places + return oldValue; + } + } + + return newValue.copyWith(text: newText); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_reciever/wallet_pay_reciever.dart b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_reciever/wallet_pay_reciever.dart new file mode 100644 index 00000000..2ddd697c --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_reciever/wallet_pay_reciever.dart @@ -0,0 +1,363 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../../../config/palette.dart'; +import '../../../../../domain_layer/entities/user_metadata.dart'; + +import '../../../../../helpers/helpers.dart'; +import '../../../../../helpers/wallet_number_formatting.dart'; +import '../../../../atoms/long_button.dart'; +import '../../../../atoms/my_profile_picture.dart'; +import '../wallet_pay_select_amount/wallet_pay_select_amount.dart'; + +import '../wallet_pay_state_provider.dart'; +import 'wallet_pay_reciever_state_provider.dart'; + +class WalletSelectReciever extends ConsumerWidget { + final String? walletId; + final Function doneCallback; + + const WalletSelectReciever({ + super.key, + this.walletId, + required this.doneCallback, + }); + + _onTokenSelected(WalletPayNotifier notifier) { + notifier.updateRecieverType(PaymentRecieverType.token); + doneCallback(); + } + + _onContactSelected({ + required WalletPayNotifier notifier, + required String pubkey, + }) { + notifier.updateRecieverType(PaymentRecieverType.contact); + notifier.updatePayToPubkey(pubkey); + doneCallback(); + } + + _onWalletSelected({ + required WalletPayNotifier notifier, + required String walletId, + }) { + notifier.updateRecieverType(PaymentRecieverType.wallet); + notifier.updatePayToWalletId(walletId); + doneCallback(); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(walletPayRecieverProvider(walletId)); + final notifier = ref.read(walletPayRecieverProvider(walletId).notifier); + + final paymentStateNotifier = ref.watch(walletPayStateProvider.notifier); + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + title: const Text('Pay to'), + leading: Container(), + leadingWidth: 0, + actions: [ + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + paymentStateNotifier.reset(); + Navigator.pop(context); + }, + ), + ], + bottom: PreferredSize( + preferredSize: const Size.fromHeight(64), + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + child: TextField( + onChanged: notifier.setSearchQuery, + decoration: InputDecoration( + isDense: true, + hintText: ' Search by name', + hintStyle: + const TextStyle(color: Colors.white, letterSpacing: 1.1), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(50.0)), + borderSide: BorderSide(color: Paletter.extraDarkGray), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(25.0)), + borderSide: + BorderSide(color: Theme.of(context).colorScheme.surface), + ), + ), + ), + ), + ), + ), + body: SafeArea( + child: state.isLoading + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + // scrollable results + Expanded( + child: ListView( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), + children: [ + if (state.searchQuery.trim().isEmpty) + _Island( + title: "Anonymous Token", + child: longButton( + name: "create token", + onPressed: () => + _onTokenSelected(paymentStateNotifier), + ), + ), + if (state.searchQuery.trim().isEmpty) + _Island( + title: 'Recent contacts', + child: _ContactsList( + contacts: state.recentContacts, + onTap: (c) => _onContactSelected( + notifier: paymentStateNotifier, + pubkey: c.pubkey, + ), + ), + ), + if (state.searchQuery.trim().isNotEmpty) + _Island( + title: 'Matching contacts', + child: _ContactsList( + contacts: state.filteredContacts, + onTap: (c) => _onContactSelected( + notifier: paymentStateNotifier, + pubkey: c.pubkey, + ), + emptyPlaceholder: const Padding( + padding: EdgeInsets.all(12.0), + child: Text('No matching contacts'), + ), + ), + ), + _Island( + title: 'Suggestions', + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _SuggestionChip( + label: 'action 1', + icon: Icons.account_balance_wallet_outlined, + onTap: () {}, + ), + _SuggestionChip( + label: 'action 2', + icon: Icons.group_outlined, + onTap: () {}, + ), + _SuggestionChip( + label: 'action 3', + icon: Icons.receipt_long_outlined, + onTap: () {}, + ), + ], + ), + ), + _Island( + title: 'All wallets', + child: _WalletsList( + wallets: state.filteredWallets, + balances: state.balances, + selectedWalletId: state.selectedWalletId, + onTap: (wallet) => _onWalletSelected( + notifier: paymentStateNotifier, + walletId: wallet.id, + ), + ), + ), + ], + ), + ), + ], + ), + ), + + /// next button + bottomNavigationBar: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), + child: SizedBox( + width: double.infinity, + height: 45, + child: longButton( + name: "next", + onPressed: () { + _onTokenSelected(paymentStateNotifier); + }, + inverted: true), + ), + ), + ), + ); + } +} + +class _Island extends StatelessWidget { + final String title; + final Widget child; + const _Island({required this.title, required this.child}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + child, + ], + ), + ), + ); + } +} + +class _ContactsList extends StatelessWidget { + final List contacts; + final void Function(UserMetadata) onTap; + final Widget? emptyPlaceholder; + + const _ContactsList({ + required this.contacts, + required this.onTap, + this.emptyPlaceholder, + }); + + @override + Widget build(BuildContext context) { + if (contacts.isEmpty) { + return emptyPlaceholder ?? + const Padding( + padding: EdgeInsets.all(12.0), + child: Text('No recent contacts'), + ); + } + return ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: contacts.length, + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (context, i) { + final c = contacts[i]; + return ListTile( + leading: SizedBox( + height: 50, + width: 50, + child: UserImage(imageUrl: c.picture, pubkey: c.pubkey), + ), + title: Text(c.name ?? ''), + subtitle: Text( + c.nip05 ?? + Helpers.shortHr( + c.pubkey, + ), + style: TextStyle(color: Paletter.gray), + ), + onTap: () => onTap(c), + ); + }, + ); + } +} + +class _WalletsList extends StatelessWidget { + final List wallets; + final List balances; + final String? selectedWalletId; + final void Function(ndk_entities.Wallet) onTap; + + const _WalletsList({ + required this.wallets, + required this.balances, + required this.selectedWalletId, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + if (wallets.isEmpty) { + return const Padding( + padding: EdgeInsets.all(12.0), + child: Text('No wallets found'), + ); + } + return ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: wallets.length, + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (context, i) { + final w = wallets[i]; + final wBallances = balances.where((b) => b.walletId == w.id); + final selected = w.id == selectedWalletId; + + /// dont show selected wallet + if (selected) { + return Container(); + } + + return ListTile( + title: Text(w.name), + subtitle: Text(w.type.toString()), + + /// show all balances for the wallet + trailing: Column(children: [ + for (final b in wBallances) + Text( + "${WalletNumberFormatting.formatAmount(amount: b.amount, unit: b.unit)} ${b.unit}", + style: TextStyle( + color: Colors.white, + ), + ), + ]), + + onTap: () => onTap(w), + ); + }, + ); + } +} + +class _SuggestionChip extends StatelessWidget { + final String label; + final IconData icon; + final VoidCallback onTap; + const _SuggestionChip({ + required this.label, + required this.icon, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return ActionChip( + avatar: Icon(icon, size: 18), + label: Text(label), + onPressed: onTap, + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_reciever/wallet_pay_reciever_state_provider.dart b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_reciever/wallet_pay_reciever_state_provider.dart new file mode 100644 index 00000000..91962eb4 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_select_reciever/wallet_pay_reciever_state_provider.dart @@ -0,0 +1,155 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:ndk/ndk.dart'; + +import '../../../../../domain_layer/entities/user_metadata.dart'; +import '../../../../../domain_layer/usecases/get_user_metadata.dart'; + +import '../../../../providers/following_contact_state_provider.dart'; +import '../../../../providers/metadata_provider.dart'; +import '../../../../providers/ndk_provider.dart'; + +class WalletPayRecieverState { + final List wallets; + final List balances; + final List allContacts; + final List recentContacts; + final String? selectedWalletId; + final String searchQuery; + final bool isLoading; + + WalletPayRecieverState({ + required this.wallets, + required this.balances, + required this.allContacts, + required this.recentContacts, + required this.selectedWalletId, + required this.searchQuery, + required this.isLoading, + }); + + WalletPayRecieverState copyWith({ + List? wallets, + List? balances, + List? allContacts, + List? recentContacts, + String? selectedWalletId, + String? searchQuery, + bool? isLoading, + }) { + return WalletPayRecieverState( + wallets: wallets ?? this.wallets, + balances: balances ?? this.balances, + allContacts: allContacts ?? this.allContacts, + recentContacts: recentContacts ?? this.recentContacts, + selectedWalletId: selectedWalletId ?? this.selectedWalletId, + searchQuery: searchQuery ?? this.searchQuery, + isLoading: isLoading ?? this.isLoading, + ); + } + + List get filteredContacts { + final q = searchQuery.trim().toLowerCase(); + if (q.isEmpty) return const []; + + return allContacts + .where((c) => + (c.name != null && c.name!.toLowerCase().contains(q)) || + (c.pubkey.toLowerCase().contains(q))) + .toList(); + } + + List get filteredWallets { + final q = searchQuery.trim().toLowerCase(); + if (q.isEmpty) return wallets; + return wallets.where((w) => w.name.toLowerCase().contains(q)).toList(); + } +} + +class WalletPayToNotifier extends StateNotifier { + final Ndk _ndk; + + final GetUserMetadata _getUserMetadata; + + final List _contactPubkeys; + + WalletPayToNotifier({ + String? initialWalletId, + required Ndk ndk, + required List contactPubkeys, + required GetUserMetadata getUserMetadata, + }) : _ndk = ndk, + _contactPubkeys = contactPubkeys, + _getUserMetadata = getUserMetadata, + super( + WalletPayRecieverState( + wallets: const [], + balances: const [], + allContacts: const [], + recentContacts: const [], + selectedWalletId: initialWalletId, + searchQuery: '', + isLoading: true, + ), + ) { + _loadInitial(); + } + + Future _loadInitial() async { + final balances = await _ndk.wallets.combinedBalances.first; + + final wallets = await _ndk.wallets.walletsStream.first; + + List contactMetadata = []; + final List futures = []; + for (final cp in _contactPubkeys) { + futures.add( + _getUserMetadata.getMetadataByPubkey(cp).last.then((metadata) { + contactMetadata.add(metadata); + }), + ); + } + //await Future.wait(futures); + + final contacts = contactMetadata; + + final recent = contactMetadata; + + String? selected = state.selectedWalletId; + // If the provided walletId isn't in the list, fall back to first. + if (selected == null || !wallets.any((w) => w.id == selected)) { + selected = wallets.isNotEmpty ? wallets.first.id : null; + } + + state = state.copyWith( + wallets: wallets, + balances: balances, + allContacts: contacts, + recentContacts: recent, + selectedWalletId: selected, + isLoading: false, + ); + } + + void setSearchQuery(String q) { + state = state.copyWith(searchQuery: q); + } +} + +final walletPayRecieverProvider = StateNotifierProvider.family< + WalletPayToNotifier, + WalletPayRecieverState, + String?>((ref, initialWalletId) { + final ndk = ref.watch(ndkProvider); + + final contacts = ref.watch(contactListSelfStateProvider); + + final getUserMetadata = ref.watch(metadataProvider); + + return WalletPayToNotifier( + initialWalletId: initialWalletId, + ndk: ndk, + contactPubkeys: contacts.contactList.contacts, + getUserMetadata: getUserMetadata, + ); +}); diff --git a/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_state_provider.dart b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_state_provider.dart new file mode 100644 index 00000000..edc641dc --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_state_provider.dart @@ -0,0 +1,270 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:ndk/ndk.dart'; + +import '../../../providers/ndk_provider.dart'; +import '../wallet_providers/wallet_combined_state_provider.dart'; + +class WalletPayState { + final List availableWallets; + final List availableBalances; + + final int? amount; + final String? unit; + + final String? memo; + + final PaymentRecieverType? recieverType; + final String? payToPubkey; + final String? payToWalletId; + final String? payFromWalletId; + final Set? supportedUnitsByWallet; + + final bool isProcessing; + final bool isError; + final bool isSuccess; + final String? errorMessage; + final ndk_entities.CashuToken? outputToken; + final String? transactionId; + + WalletPayState({ + required this.availableWallets, + required this.availableBalances, + required this.payFromWalletId, + required this.amount, + required this.unit, + required this.memo, + required this.recieverType, + required this.payToPubkey, + required this.payToWalletId, + this.supportedUnitsByWallet, + this.isProcessing = false, + this.isError = false, + this.isSuccess = false, + this.errorMessage, + this.outputToken, + this.transactionId, + }); + + ndk_entities.Wallet? get payFromWallet { + if (payFromWalletId == null) return null; + final filter = availableWallets.where( + (wallet) => wallet.id == payFromWalletId, + ); + if (filter.isEmpty) return null; + return filter.first; + } + + WalletPayState copyWith({ + List? availableWallets, + List? availableBalances, + String? payFromWalletId, + int? amount, + String? unit, + String? memo, + PaymentRecieverType? recieverType, + String? payToPubkey, + String? payToWalletId, + Set? supportedUnitsByWallet, + bool? isProcessing, + bool? isError, + bool? isSuccess, + String? errorMessage, + ndk_entities.CashuToken? outputToken, + String? transactionId, + }) { + return WalletPayState( + availableWallets: availableWallets ?? this.availableWallets, + availableBalances: availableBalances ?? this.availableBalances, + payFromWalletId: payFromWalletId ?? this.payFromWalletId, + amount: amount ?? this.amount, + unit: unit ?? this.unit, + memo: memo ?? this.memo, + recieverType: recieverType ?? this.recieverType, + payToPubkey: payToPubkey ?? this.payToPubkey, + payToWalletId: payToWalletId ?? this.payToWalletId, + supportedUnitsByWallet: + supportedUnitsByWallet ?? this.supportedUnitsByWallet, + isProcessing: isProcessing ?? this.isProcessing, + isError: isError ?? this.isError, + isSuccess: isSuccess ?? this.isSuccess, + errorMessage: errorMessage ?? this.errorMessage, + outputToken: outputToken ?? this.outputToken, + transactionId: transactionId ?? this.transactionId, + ); + } +} + +enum PaymentRecieverType { + token('token'), + contact('contact'), + wallet('wallet'); + + final String value; + + const PaymentRecieverType(this.value); + + factory PaymentRecieverType.fromValue(String value) { + return PaymentRecieverType.values.firstWhere( + (kind) => kind.value == value, + orElse: () => throw ArgumentError('Invalid event kind value: $value'), + ); + } + + @override + String toString() => value; +} + +class WalletPayNotifier extends StateNotifier { + final Ndk _ndk; + final Ref ref; + + WalletPayNotifier({required Ndk ndk, required this.ref}) + : _ndk = ndk, + super( + WalletPayState( + availableWallets: [], + availableBalances: [], + payFromWalletId: null, + amount: null, + unit: null, + memo: null, + recieverType: null, + payToPubkey: null, + payToWalletId: null, + ), + ) { + // listen to state changes + ref.listen(walletCombinedProvider, (previous, next) { + if (next.balances != previous?.balances || + next.wallets != previous?.wallets) { + state = state.copyWith( + availableBalances: next.balances, + availableWallets: next.wallets, + ); + } + }, fireImmediately: true); + } + + bool get valid { + return state.payFromWalletId != null && + state.amount != null && + state.unit != null && + state.recieverType != null && + (state.recieverType == PaymentRecieverType.contact + ? state.payToPubkey != null + : state.recieverType == PaymentRecieverType.wallet + ? state.payToWalletId != null + : true); + } + + void updatePayFromWalletId(String walletId) { + state = state.copyWith( + payFromWalletId: walletId, + ); + state = state.copyWith( + supportedUnitsByWallet: state.payFromWallet?.supportedUnits, + unit: + state.unit == null ? state.payFromWallet?.supportedUnits.first : null, + ); + } + + void updateAmount(int amount) { + state = state.copyWith(amount: amount); + print('Updated amount: $amount'); + } + + void updateUnit(String unit) { + state = state.copyWith(unit: unit); + print('Updated unit: $unit'); + } + + void updateMemo(String? memo) { + state = state.copyWith(memo: memo); + } + + void updateRecieverType(PaymentRecieverType type) { + state = state.copyWith(recieverType: type); + } + + void updatePayToPubkey(String? pubkey) { + state = state.copyWith(payToPubkey: pubkey); + } + + void updatePayToWalletId(String? walletId) { + state = state.copyWith(payToWalletId: walletId); + } + + void setProcessing({bool isProcessing = true}) { + state = state.copyWith(isProcessing: isProcessing); + } + + void setError({bool isError = true, String? errorMessage}) { + state = state.copyWith( + isError: isError, + errorMessage: errorMessage, + isProcessing: false, + ); + } + + void setSuccessToken({ + bool isSuccess = true, + required ndk_entities.CashuToken outputToken, + String? transactionId, + }) { + state = state.copyWith( + isSuccess: isSuccess, + outputToken: outputToken, + isProcessing: false, + transactionId: transactionId, + ); + } + + void createToken({String? memo}) async { + try { + final result = await _ndk.cashu.initiateSpend( + mintUrl: state.payFromWallet!.id, + amount: state.amount!, + unit: state.unit!, + memo: memo, + ); + + setSuccessToken( + outputToken: result.token, + transactionId: result.transaction.id, + ); + } catch (e) { + setError( + errorMessage: e.toString(), + ); + + return; + } + } + + void reset() { + state = WalletPayState( + availableBalances: state.availableBalances, + availableWallets: state.availableWallets, + payFromWalletId: null, + amount: null, + unit: null, + memo: null, + recieverType: null, + payToPubkey: null, + payToWalletId: null, + supportedUnitsByWallet: null, + isProcessing: false, + isError: false, + isSuccess: false, + errorMessage: null, + outputToken: null, + ); + } +} + +final walletPayStateProvider = + StateNotifierProvider((ref) { + final ndk = ref.watch(ndkProvider); + return WalletPayNotifier(ndk: ndk, ref: ref); +}); diff --git a/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_summary/wallet_pay_summary.dart b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_summary/wallet_pay_summary.dart new file mode 100644 index 00000000..56c9dd97 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_pay/wallet_pay_summary/wallet_pay_summary.dart @@ -0,0 +1,433 @@ +import 'package:camelus/presentation_layer/atoms/long_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../../../config/palette.dart'; +import '../../../../../helpers/wallet_number_formatting.dart'; +import '../../../../atoms/wallet/wallet_card.dart'; +import '../../../../components/wallet/wallets_select_bottom_sheet.dart'; +import '../wallet_pay_done/wallet_pay_done.dart'; +import '../wallet_pay_state_provider.dart'; + +class WalletPaySummary extends ConsumerWidget { + final Function backCallback; + const WalletPaySummary({ + super.key, + required this.backCallback, + }); + + showSnackBar(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: Theme.of(context).colorScheme.error, + content: Text( + message, + style: TextStyle(color: Colors.white), + ), + ), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(walletPayStateProvider); + final payNotifier = ref.read(walletPayStateProvider.notifier); + + bool isValid() { + if (state.payFromWalletId == null || state.payFromWalletId!.isEmpty) { + showSnackBar(context, 'Please select a wallet'); + return false; + } + if (state.amount == null || state.amount! <= 0) { + showSnackBar(context, 'Please enter a valid amount'); + return false; + } + if (state.unit == null || state.unit!.isEmpty) { + showSnackBar(context, 'Please select a unit'); + return false; + } + //! only token support for now + if (state.recieverType != PaymentRecieverType.token) { + showSnackBar(context, 'Only token payments are supported for now'); + return false; + } + + final availableBalanceForUnit = state.availableBalances.where( + (balance) => + balance.walletId == state.payFromWalletId && + balance.unit == state.unit, + ); + if (availableBalanceForUnit.isEmpty || + availableBalanceForUnit.first.amount < state.amount!) { + showSnackBar(context, + 'Insufficient balance in the selected wallet for the specified unit'); + return false; + } + + return true; + } + + void onSend() async { + final stateNotifier = ref.read(walletPayStateProvider.notifier); + if (!isValid()) return; + + stateNotifier.createToken(memo: state.memo); + + /// navigate to done page + + context.pushReplacement('/wallet/pay/done'); + } + + return Scaffold( + backgroundColor: Colors.black, + body: Column( + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + /// header back, close + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon( + Icons.arrow_back, + color: Colors.white, + size: 24, + ), + onPressed: () => backCallback(), + ), + Text( + 'summary', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + IconButton( + icon: Icon( + Icons.close, + color: Paletter.lightGray, + size: 24, + ), + onPressed: () { + payNotifier.reset(); + context.pop(); + }, + ), + ], + ), + SizedBox(height: 40), + + /// amount section + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 30), + Text( + state.amount != null && state.unit != null + ? WalletNumberFormatting.formatAmount( + amount: state.amount!, unit: state.unit!) + : '0', + style: TextStyle( + color: Colors.white, + fontSize: 48, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(width: 4), + IconButton( + icon: Icon( + PhosphorIcons.notePencil(), + size: 24, + color: Paletter.extraLightGray, + ), + onPressed: () => backCallback(), + ), + ], + ), + Text( + state.unit ?? '', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 20), + ], + ), + ), + ), + ), + + /// main + Expanded( + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: Color(0xFF2A2A2A), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + WalletCard( + showBalances: false, + backgroundColor: Colors.transparent, + wallet: state.availableWallets.firstWhere( + (w) => w.id == state.payFromWalletId, + orElse: () => ndk_entities.CashuWallet( + id: '', + name: 'Select Wallet', + type: ndk_entities.WalletType.CASHU, + supportedUnits: {}, + mintUrl: '', + mintInfo: ndk_entities.CashuMintInfo(nuts: {}), + ), + ), + balances: state.availableBalances + .where((b) => b.walletId == state.payFromWalletId) + .toList(), + onTap: (_) {}, + tralling: IconButton( + icon: Icon( + PhosphorIcons.notePencil(), + size: 25, + ), + color: Theme.of(context).colorScheme.primary, + onPressed: () async { + final selectedId = + await showWalletsSelectBottomSheet( + context: context, + selectedId: state.payFromWalletId, + wallets: state.availableWallets, + balances: state.availableBalances, + ); + if (selectedId != null) { + payNotifier.updatePayFromWalletId(selectedId); + } + }, + ), + ), + + Divider(color: Paletter.darkGray, height: 1), + + /// receiver + if (state.recieverType == + PaymentRecieverType.contact) ...[ + ContactReciever(), + ] else if (state.recieverType == + PaymentRecieverType.token) ...[ + TokenReciever(), + ] else if (state.recieverType == + PaymentRecieverType.wallet) ...[ + WalletReciever(), + ], + ], + ), + ), + + SizedBox(height: 16), + + /// details + _buildDetailRow( + label: 'transaction type', + value: state.recieverType.toString(), + isEditable: false, + context: context), + + _buildDetailRow( + label: 'Memo', + value: state.memo ?? '', + onEdit: () { + backCallback(); + }, + context: context), + + SizedBox(height: 24), + + Spacer(), + ], + ), + ), + ), + ], + ), + bottomNavigationBar: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: longButton( + name: "send", onPressed: () => onSend(), inverted: true), + ), + ), + ); + } + + Widget _buildDetailRow({ + required String label, + required String value, + bool isEditable = true, + Function()? onEdit, + required BuildContext context, + }) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: TextStyle(color: Colors.grey, fontSize: 12)), + Text(value, style: TextStyle(color: Colors.white, fontSize: 16)), + ], + ), + if (isEditable && onEdit != null) + IconButton( + icon: Icon( + PhosphorIcons.notePencil(), + size: 25, + ), + color: Theme.of(context).colorScheme.primary, + onPressed: isEditable ? onEdit : null, + ), + ], + ), + ); + } +} + +class ContactReciever extends StatelessWidget { + const ContactReciever({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + backgroundColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), + child: + Text('NA', style: TextStyle(color: Colors.white, fontSize: 12)), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('receiver', + style: TextStyle(color: Colors.grey, fontSize: 12)), + Text('NOT IMPLEMENTED', + style: TextStyle(color: Colors.white, fontSize: 16)), + Text('send to pubkey not implemented', + style: TextStyle(color: Colors.grey, fontSize: 14)), + ], + ), + ), + ], + ), + ); + } +} + +class TokenReciever extends StatelessWidget { + const TokenReciever({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + backgroundColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), + child: + Text('TK', style: TextStyle(color: Colors.white, fontSize: 12)), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('receiver', + style: TextStyle(color: Paletter.gray, fontSize: 12)), + Text('Token', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold)), + Text('send as a token or qr code', + style: TextStyle(color: Paletter.lightGray, fontSize: 14)), + ], + ), + ), + ], + ), + ); + } +} + +class WalletReciever extends StatelessWidget { + const WalletReciever({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + backgroundColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), + child: + Text('NA', style: TextStyle(color: Colors.white, fontSize: 12)), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('receiver', + style: TextStyle(color: Colors.grey, fontSize: 12)), + Text('NOT IMPLEMENTED', + style: TextStyle(color: Colors.white, fontSize: 16)), + Text('send to wallet not implemented', + style: TextStyle(color: Colors.grey, fontSize: 14)), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_providers/qr_value_processing_state_provider.dart b/lib/presentation_layer/routes/wallet/wallet_providers/qr_value_processing_state_provider.dart new file mode 100644 index 00000000..49bd34cd --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_providers/qr_value_processing_state_provider.dart @@ -0,0 +1,128 @@ +import 'package:riverpod/riverpod.dart'; + +class QRScannerState { + final bool isProcessing; + final String? error; + final QRNavigationTarget? navigationTarget; + final Map? navigationData; + + QRScannerState({ + required this.isProcessing, + required this.error, + required this.navigationTarget, + required this.navigationData, + }); + + QRScannerState copyWith({ + bool? isProcessing, + String? error, + QRNavigationTarget? navigationTarget, + Map? navigationData, + }) { + return QRScannerState( + isProcessing: isProcessing ?? this.isProcessing, + error: error ?? this.error, + navigationTarget: navigationTarget ?? this.navigationTarget, + navigationData: navigationData ?? this.navigationData, + ); + } +} + +enum QRNavigationTarget { + rcvPage, + sendPage, +} + +class QRScanTypeResult { + final String value; + final QRScanTypes type; + + QRScanTypeResult({ + required this.value, + required this.type, + }); +} + +enum QRScanTypes { + cashuToken, + lightningInvoice, + unknown, +} + +class QRScannerNotifier extends StateNotifier { + QRScannerNotifier() + : super( + QRScannerState( + isProcessing: false, + error: null, + navigationTarget: null, + navigationData: null, + ), + ); + + void setError(String error) { + state = state.copyWith(error: error); + } + + Future processQRCode(String qrData) async { + state = state.copyWith(isProcessing: true, error: null); + + final result = await _analyzeQRData(qrData); + + if (result.type == QRScanTypes.cashuToken) { + state = state.copyWith( + isProcessing: false, + navigationTarget: QRNavigationTarget.rcvPage, + navigationData: {'cashuTokenString': result.value}, + ); + } else if (result.type == QRScanTypes.lightningInvoice) { + state = state.copyWith( + isProcessing: false, + navigationTarget: QRNavigationTarget.sendPage, + navigationData: {'lightningInvoice': result.value}, + ); + } else { + state = state.copyWith( + isProcessing: false, + error: 'Unsupported QR code type', + ); + } + } + + void clearNavigation() { + state = state.copyWith(navigationTarget: null, navigationData: null); + } + + void reset() { + state = state.copyWith( + isProcessing: false, + error: null, + navigationTarget: null, + navigationData: null, + ); + } + + Future _analyzeQRData(String qrData) async { + // Analyze the QR data and return the appropriate type + if (qrData.startsWith('cashuB')) { + return QRScanTypeResult( + value: qrData, + type: QRScanTypes.cashuToken, + ); + } else if (qrData.startsWith('lightning:')) { + return QRScanTypeResult( + value: qrData, + type: QRScanTypes.lightningInvoice, + ); + } + return QRScanTypeResult( + value: qrData, + type: QRScanTypes.unknown, + ); + } +} + +final qrScannerProvider = + StateNotifierProvider.autoDispose( + (ref) => QRScannerNotifier(), +); diff --git a/lib/presentation_layer/routes/wallet/wallet_providers/wallet_combined_state_provider.dart b/lib/presentation_layer/routes/wallet/wallet_providers/wallet_combined_state_provider.dart new file mode 100644 index 00000000..9a041a77 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_providers/wallet_combined_state_provider.dart @@ -0,0 +1,93 @@ +import 'dart:async'; + +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:ndk/ndk.dart'; + +import 'package:riverpod/riverpod.dart'; + +import '../../../providers/db_ndk_provider.dart'; +import '../../../providers/ndk_provider.dart'; + +/// class for to save state for each post if it is reposted or not +class WalletCombinedState { + final List balances; + final List wallets; + final List recentTransactions; + final List pendingTransactions; + + WalletCombinedState({ + required this.balances, + required this.recentTransactions, + required this.pendingTransactions, + required this.wallets, + }); + + WalletCombinedState copyWith({ + List? balances, + List? recentTransactions, + List? pendingTransactions, + List? wallets, + }) { + return WalletCombinedState( + balances: balances ?? this.balances, + recentTransactions: recentTransactions ?? this.recentTransactions, + pendingTransactions: pendingTransactions ?? this.pendingTransactions, + wallets: wallets ?? this.wallets, + ); + } +} + +final walletCombinedProvider = StateNotifierProvider.autoDispose< + WalletCombinedStateNotifier, WalletCombinedState>( + (ref) { + final ndk = ref.watch(ndkProvider); + final ndkDb = ref.watch(dbNdkProvider)!; + + return WalletCombinedStateNotifier(ndk, ndkDb); + }, +); + +class WalletCombinedStateNotifier extends StateNotifier { + final Ndk _ndk; + final CacheManager ndkDb; + + final _subscriptions = []; + + WalletCombinedStateNotifier( + this._ndk, + this.ndkDb, + ) : super( + WalletCombinedState( + balances: [], + recentTransactions: [], + pendingTransactions: [], + wallets: []), + ) { + _initializeState(); + } + + @override + void dispose() { + for (final subscription in _subscriptions) { + subscription.cancel(); + } + super.dispose(); + } + + Future _initializeState() async { + _subscriptions.addAll([ + _ndk.wallets.combinedBalances.listen((data) { + state = state.copyWith(balances: data); + }), + _ndk.wallets.combinedRecentTransactions.listen((data) { + state = state.copyWith(recentTransactions: data); + }), + _ndk.wallets.combinedPendingTransactions.listen((data) { + state = state.copyWith(pendingTransactions: data); + }), + _ndk.wallets.walletsStream.listen((data) { + state = state.copyWith(wallets: data); + }), + ]); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_qr_scan.dart b/lib/presentation_layer/routes/wallet/wallet_qr_scan.dart new file mode 100644 index 00000000..2a9eed77 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_qr_scan.dart @@ -0,0 +1,249 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../../config/palette.dart'; +import '../../atoms/spinner_center.dart'; +import 'wallet_navigation.dart'; +import 'wallet_providers/qr_value_processing_state_provider.dart'; +import 'wallet_receive/rcv_completers/wallet_rcv_ecash_completer_state_provider.dart'; + +class WalletQrScan extends ConsumerStatefulWidget { + const WalletQrScan({super.key}); + + @override + ConsumerState createState() => _QrScan(); +} + +class _QrScan extends ConsumerState { + Barcode? _barcode; + MobileScannerController controller = MobileScannerController(); + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + + super.dispose(); + } + + void _handleBarcode(BarcodeCapture barcodes) { + if (mounted) { + setState(() { + _barcode = barcodes.barcodes.firstOrNull; + }); + } + if (_barcode != null) { + _processValue(_barcode!.rawValue!); + } + } + + Future _handleReadClipboard() async { + try { + ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); + return data?.text; + } catch (e) { + ref + .read(qrScannerProvider.notifier) + .setError('Failed to read clipboard: $e'); + + return null; + } + } + + void _processValue(String barcode) { + ref.read(qrScannerProvider.notifier).processQRCode(barcode); + } + + @override + Widget build(BuildContext context) { + final qrScannerState = ref.watch(qrScannerProvider); + + // apperently this is save to do with riverpod + ref.listen(qrScannerProvider, (previous, next) { + // only navigate if its new + if (previous?.navigationTarget != next.navigationTarget && + next.navigationTarget != null && + next.navigationData != null) { + ref.read(qrScannerProvider.notifier).clearNavigation(); + + final rcvProvider = + ref.read(walletReceiveEcashCompleterProvider.notifier); + + switch (next.navigationTarget!) { + case QRNavigationTarget.rcvPage: + final ecashTokenString = + next.navigationData!['cashuTokenString'] as String; + rcvProvider.receiveEcash(tokenString: ecashTokenString); + + context.push('/wallet/receive/ecash'); + ref.read(walletNavigationProvider.notifier).changeDashboardPage(1); + break; + case QRNavigationTarget.sendPage: + throw UnimplementedError( + 'Send page navigation is not implemented yet'); + break; + } + } + }); + + return Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + MobileScanner( + controller: controller, + onDetect: _handleBarcode, + errorBuilder: (p0, p1) { + return Center( + child: Text( + 'Error: $p1', + style: TextStyle( + color: Theme.of(context).colorScheme.error, fontSize: 16), + ), + ); + }, + placeholderBuilder: (context) { + return SpinnerCenter(); + }, + ), + + // Container( + // width: MediaQuery.of(context).size.width, + // height: MediaQuery.of(context).size.height, + // decoration: BoxDecoration( + // border: Border.lerp( + // Border.all(color: Colors.black, width: 10), + // Border.all(color: Colors.transparent, width: 1), + // 0.5, + // ), + // borderRadius: BorderRadius.circular(40), + // ), + // ), + + /// scanning indicator + Center( + child: Container( + width: 250, + height: 250, + decoration: BoxDecoration( + border: Border.all( + color: Colors.white, + width: 2, + ), + borderRadius: BorderRadius.circular(20), + ), + child: null), + ), + + /// bottom area + Align( + alignment: Alignment.bottomCenter, + child: Container( + alignment: Alignment.bottomCenter, + height: 120, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.8), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton.icon( + onPressed: () { + _handleReadClipboard().then((value) { + if (value != null) { + _processValue(value); + } + }); + }, + icon: Icon( + PhosphorIcons.clipboardText(), + size: 18, + ), + label: const Text('paste from clipboard'), + style: ElevatedButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.primary, + foregroundColor: Colors.white, + ), + ), + ], + ), + ], + ), + ), + ), + ), + if (qrScannerState.isProcessing) + Positioned( + bottom: 115, + left: 20, + right: 20, + child: LinearProgressIndicator( + borderRadius: BorderRadius.circular(20), + backgroundColor: Paletter.extraDarkGray, + color: Colors.white, + ), + ), + + /// top controls + Positioned( + top: 20, + left: 20, + right: 20, + child: SafeArea( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + qrScannerState.error != null + ? Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(50), + ), + child: Text( + qrScannerState.error ?? "", + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + ) + : const SizedBox.shrink(), + IconButton( + onPressed: () => controller.toggleTorch(), + icon: const Icon(Icons.flash_on, color: Colors.white), + style: IconButton.styleFrom( + backgroundColor: Colors.black54, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_receive/rcv_completers/wallet_rcv_ecash_completer_page.dart b/lib/presentation_layer/routes/wallet/wallet_receive/rcv_completers/wallet_rcv_ecash_completer_page.dart new file mode 100644 index 00000000..bb33030c --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_receive/rcv_completers/wallet_rcv_ecash_completer_page.dart @@ -0,0 +1,231 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../../../../config/palette.dart'; +import '../../../../../helpers/wallet_number_formatting.dart'; +import '../../../../atoms/long_button.dart'; +import 'wallet_rcv_ecash_completer_state_provider.dart'; + +class WalletReceiveEcashCompleterPage extends ConsumerWidget { + const WalletReceiveEcashCompleterPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final walletState = ref.watch(walletReceiveEcashCompleterProvider); + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: null, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Spacer(flex: 1), + // Status Card + _buildStatusCard(walletState, context), + + Spacer(flex: 7), + + if (walletState.amount != null && walletState.unit != null) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '+', + style: const TextStyle( + color: Paletter.lightGray, + fontSize: 28, + ), + ), + const SizedBox(width: 8), + Text( + WalletNumberFormatting.formatAmount( + amount: walletState.amount!, unit: walletState.unit!), + style: const TextStyle( + color: Colors.white, + fontSize: 48, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Text( + walletState.unit ?? '', + style: const TextStyle( + fontSize: 16, color: Paletter.lightGray), + ), + ], + ), + ], + Spacer(flex: 1), + if (walletState.memo != null) ...[ + const SizedBox(height: 16), + Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width - 32, + // max height for memo + maxHeight: 120, + ), + child: SingleChildScrollView( + child: Text( + walletState.memo!, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + + if (walletState.errorMessage != null) ...[ + Spacer(flex: 1), + _buildErrorCard(walletState.errorMessage!), + ], + Spacer(flex: 10), + ], + ), + ), + ), + bottomNavigationBar: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: longButton( + name: "close", onPressed: () => {context.pop()}, inverted: true), + ), + ), + ); + } + + Widget _buildStatusCard( + WalletRcvEcashCompleterState state, BuildContext context) { + IconData icon; + Color color; + String title; + String subtitle; + + if (state.isPending) { + icon = PhosphorIcons.hourglass(); + color = Theme.of(context).colorScheme.primary; + title = 'Processing'; + subtitle = 'Receiving...'; + } else if (state.isError) { + icon = PhosphorIcons.warningCircle(); + color = Theme.of(context).colorScheme.error; + title = 'Failed'; + subtitle = 'transaction failed'; + } else if (state.isSuccess) { + icon = PhosphorIcons.checkCircle(); + color = Colors.green; + title = 'Success'; + subtitle = 'received successfully'; + } else { + icon = PhosphorIcons.info(); + color = Paletter.gray; + title = 'Ready'; + subtitle = 'waiting for transaction'; + } + + return Card( + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(50), + ), + child: Icon( + icon, + color: color, + size: 32, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + if (state.isPending) + const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ], + ), + ), + ); + } + + Widget _buildErrorCard(String errorMessage) { + return Card( + elevation: 2, + color: Colors.red[50], + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.error_outline, + color: Colors.red[700], + size: 24, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Error', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.red[700], + ), + ), + const SizedBox(height: 4), + Text( + errorMessage, + style: TextStyle( + fontSize: 14, + color: Colors.red[600], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_receive/rcv_completers/wallet_rcv_ecash_completer_state_provider.dart b/lib/presentation_layer/routes/wallet/wallet_receive/rcv_completers/wallet_rcv_ecash_completer_state_provider.dart new file mode 100644 index 00000000..33c1a56f --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_receive/rcv_completers/wallet_rcv_ecash_completer_state_provider.dart @@ -0,0 +1,148 @@ +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:ndk/ndk.dart'; +import 'package:riverpod/riverpod.dart'; + +import '../../../../providers/ndk_provider.dart'; + +enum WalletRcvType { + eCash, +} + +class WalletRcvEcashCompleterState { + final bool isPending; + final bool isCompleted; + final bool isError; + final bool isSuccess; + final String? errorMessage; + + final String? eCashTokenString; + final WalletRcvType? type; + + final String? memo; + final int? amount; + final String? unit; + + WalletRcvEcashCompleterState({ + required this.isPending, + required this.isCompleted, + required this.isError, + required this.isSuccess, + this.errorMessage, + this.memo, + this.eCashTokenString, + this.type, + this.amount, + this.unit, + }); + + WalletRcvEcashCompleterState copyWith({ + bool? isPending, + bool? isCompleted, + bool? isError, + bool? isSuccess, + String? errorMessage, + String? memo, + String? eCashTokenString, + WalletRcvType? type, + int? amount, + String? unit, + }) { + return WalletRcvEcashCompleterState( + isPending: isPending ?? this.isPending, + isCompleted: isCompleted ?? this.isCompleted, + isError: isError ?? this.isError, + isSuccess: isSuccess ?? this.isSuccess, + errorMessage: errorMessage ?? this.errorMessage, + memo: memo ?? this.memo, + eCashTokenString: eCashTokenString ?? this.eCashTokenString, + type: type ?? this.type, + amount: amount ?? this.amount, + unit: unit ?? this.unit, + ); + } +} + +class WalletRcvEcashCompleterNotifier + extends StateNotifier { + final Ndk _ndk; + WalletRcvEcashCompleterNotifier({required Ndk ndk}) + : _ndk = ndk, + super( + WalletRcvEcashCompleterState( + isPending: false, + isCompleted: false, + isError: false, + isSuccess: false, + ), + ); + + void receiveEcash({ + required String tokenString, + }) async { + reset(); + + state = state.copyWith(isPending: true); + try { + final rcvResultStream = _ndk.cashu.receive(tokenString); + + await for (final rcvResult in rcvResultStream) { + if (rcvResult.state == ndk_entities.WalletTransactionState.pending) { + state = state.copyWith( + isPending: true, + eCashTokenString: tokenString, + type: WalletRcvType.eCash, + amount: rcvResult.changeAmount, + unit: rcvResult.unit, + memo: rcvResult.note, + ); + } else if (rcvResult.state == + ndk_entities.WalletTransactionState.completed) { + state = state.copyWith( + isSuccess: true, + isCompleted: true, + isPending: false, + ); + } else if (rcvResult.state == + ndk_entities.WalletTransactionState.failed) { + state = state.copyWith( + isError: true, + isCompleted: true, + isPending: false, + errorMessage: '${rcvResult.completionMsg}', + ); + } + } + } catch (e) { + state = state.copyWith( + isPending: false, + isError: true, + isCompleted: true, + errorMessage: '$e', + ); + return; + } + } + + void reset() { + state = WalletRcvEcashCompleterState( + isPending: false, + isCompleted: false, + isError: false, + isSuccess: false, + errorMessage: null, + eCashTokenString: null, + type: null, + memo: null, + amount: null, + unit: null, + ); + } +} + +final walletReceiveEcashCompleterProvider = StateNotifierProvider< + WalletRcvEcashCompleterNotifier, WalletRcvEcashCompleterState>( + (ref) { + final ndk = ref.watch(ndkProvider); + return WalletRcvEcashCompleterNotifier(ndk: ndk); + }, +); diff --git a/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_amout/wallet_receive_amount.dart b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_amout/wallet_receive_amount.dart new file mode 100644 index 00000000..fc45edc8 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_amout/wallet_receive_amount.dart @@ -0,0 +1,349 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import '../../../../../config/palette.dart'; +import '../../../../atoms/currency_picker_bar.dart'; +import '../../../../atoms/long_button.dart'; +import '../../../../atoms/wallet/wallet_card.dart'; +import '../../../../components/wallet/wallets_select_bottom_sheet.dart'; +import '../../wallet_providers/wallet_combined_state_provider.dart'; + +import '../wallet_receive_state_provider.dart'; + +class WalletReceiveAmount extends ConsumerStatefulWidget { + final Function doneCallback; + final Function backCallback; + + const WalletReceiveAmount({ + super.key, + required this.doneCallback, + required this.backCallback, + this.title, + }); + + final String? title; + + @override + ConsumerState createState() => + _WalletPaySelectAmountState(); +} + +class _WalletPaySelectAmountState extends ConsumerState { + late final TextEditingController _amountController; + late final TextEditingController _memoController; + final FocusNode _amountFocus = FocusNode(); + final FocusNode _memoFocus = FocusNode(); + + @override + void initState() { + super.initState(); + final state = ref.read(walletRecieverProvider); + _amountController = TextEditingController(text: state.amount?.toString()); + _memoController = TextEditingController(text: state.memo); + + _amountController.addListener(() { + final stateR = ref.read(walletRecieverProvider); + if (_amountController.text.isEmpty) { + return; + } + + final int? parsedAmount; + if (stateR.unit == 'sat') { + parsedAmount = int.tryParse(_amountController.text); + } else { + // Handle decimal input for fiat currencies + final sanitizedInput = _amountController.text.replaceAll(',', '.'); + parsedAmount = ((double.tryParse(sanitizedInput) ?? 0) * 100).toInt(); + } + + ref.read(walletRecieverProvider.notifier).updateAmount(parsedAmount ?? 0); + }); + _memoController.addListener(() { + ref + .read(walletRecieverProvider.notifier) + .updateMemo(_memoController.text); + }); + } + + @override + void dispose() { + _amountController.dispose(); + _memoController.dispose(); + _amountFocus.dispose(); + _memoFocus.dispose(); + super.dispose(); + } + + _onSwitchCurrency({ + required String previousUnit, + required String currentUnit, + }) { + final state = ref.read(walletRecieverProvider); + final stateNotifier = ref.read(walletRecieverProvider.notifier); + + if (previousUnit == "sat" && currentUnit != "sat") { + /// sat to fiat + final amount = state.amount; + if (amount != null) { + final convertedAmount = (amount).toStringAsFixed(2); + _amountController.text = convertedAmount; + stateNotifier.updateAmount((amount * 100).toInt()); + } + } else if (previousUnit != "sat" && currentUnit == "sat") { + /// fiat to sat + final amount = state.amount; + if (amount != null) { + final convertedAmount = (amount / 100).round(); + _amountController.text = convertedAmount.toString(); + stateNotifier.updateAmount(convertedAmount); + } + } else { + /// fiat to fiat + } + } + + showSnackBar(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: Theme.of(context).colorScheme.error, + content: Text( + message, + style: TextStyle(color: Colors.white), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final state = ref.watch(walletRecieverProvider); + final notifier = ref.read(walletRecieverProvider.notifier); + + final combinedWallets = ref.watch(walletCombinedProvider); + + final List mySelectedWalletList = + combinedWallets.wallets + .where( + (w) => w.id == state.recieveToWalletId, + ) + .toList(); + + final ndk_entities.CashuWallet? mySelectedWallet = + mySelectedWalletList.isNotEmpty + ? mySelectedWalletList.first as ndk_entities.CashuWallet + : null; + + final List supportedUnitsBySelectedWallet = + mySelectedWallet?.supportedUnits.toList() ?? []; + + return Scaffold( + appBar: widget.title != null + ? AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + title: Text(widget.title!), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + widget.backCallback(); + }, + ), + ) + : null, + backgroundColor: Theme.of(context).colorScheme.surface, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 24, 16, 16), + child: Column( + children: [ + Container( + child: WalletCard( + wallet: combinedWallets.wallets.firstWhere( + (w) => w.id == state.recieveToWalletId, + orElse: () => ndk_entities.CashuWallet( + id: '', + name: 'Select Wallet', + type: ndk_entities.WalletType.CASHU, + supportedUnits: {}, + mintUrl: '', + mintInfo: ndk_entities.CashuMintInfo(nuts: {}), + ), + ), + balances: combinedWallets.balances + .where((b) => b.walletId == state.recieveToWalletId) + .toList(), + onTap: (_) {}, + tralling: IconButton( + icon: Icon( + PhosphorIcons.notePencil(), + size: 25, + ), + color: Theme.of(context).colorScheme.primary, + onPressed: () async { + final selectedId = await showWalletsSelectBottomSheet( + context: context, + selectedId: state.recieveToWalletId, + wallets: combinedWallets.wallets, + balances: combinedWallets.balances, + ); + if (selectedId != null) { + notifier.updateRecieveToWalletId(selectedId); + } + }, + ), + ), + ), + + Spacer(flex: 1), + TextField( + controller: _amountController, + focusNode: _amountFocus, + autofocus: true, + keyboardType: TextInputType.numberWithOptions( + decimal: state.unit != 'sat'), + inputFormatters: state.unit == 'sat' + ? [ + FilteringTextInputFormatter.digitsOnly, + ] + : [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9\.,]')), + DecimalTextInputFormatter(decimalRange: 2), + ], + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.bold, + ), + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.unit != 'sat' ? '0.00' : '0', + ), + ), + + const SizedBox(height: 12), + Spacer(flex: 1), + + /// currency selector + Align( + alignment: Alignment.center, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 320), + child: CurrencyPickerBar( + currencies: supportedUnitsBySelectedWallet, + initialIndex: state.unit != null + ? supportedUnitsBySelectedWallet.indexOf(state.unit!) + : 0, + onChanged: (r) => { + notifier.updateUnit( + r.currentUnit, + ), + _onSwitchCurrency( + previousUnit: r.previousUnit, + currentUnit: r.currentUnit, + ), + }, + showHaptics: true, + trackColor: Paletter.extraDarkGray, + activeColor: Theme.of(context).colorScheme.primary, + ), + ), + ), + + Spacer(flex: 5), + + /// memo input + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), + child: TextField( + controller: _memoController, + focusNode: _memoFocus, + keyboardType: TextInputType.text, + maxLines: 3, + minLines: 1, + decoration: const InputDecoration( + labelText: 'Memo', + hintText: 'Add a memo (optional)', + border: OutlineInputBorder(), + ), + ), + ), + Spacer(flex: 1), + ], + ), + ), + ), + bottomNavigationBar: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), + child: SizedBox( + width: double.infinity, + height: 40, + child: longButton( + name: "create request", + onPressed: () { + if (state.recieveToWalletId == null || + state.recieveToWalletId!.isEmpty) { + showSnackBar( + context, 'Please select a wallet to receive to.'); + return; + } + if (state.amount == null || state.amount! <= 0) { + _amountFocus.requestFocus(); + showSnackBar(context, 'Please enter a valid amount.'); + return; + } + notifier.mintEcashToken(); + widget.doneCallback(); + }, + inverted: true), + ), + ), + ), + ); + } +} + +class DecimalTextInputFormatter extends TextInputFormatter { + DecimalTextInputFormatter({required this.decimalRange}) + : assert(decimalRange > 0); + + final int decimalRange; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + String newText = newValue.text; + + newText = newText.replaceAll(',', '.'); + + if (newText.isEmpty) { + return newValue.copyWith(text: ''); + } + + if (newText == '.') { + return newValue.copyWith(text: '0.'); + } + + if (!RegExp(r'^\d*\.?\d*').hasMatch(newText)) { + return oldValue; + } + + if (newText.contains('.')) { + List parts = newText.split('.'); + if (parts.length > 2) { + return oldValue; + } + if (parts[1].length > decimalRange) { + // too many decimal places + return oldValue; + } + } + + return newValue.copyWith(text: newText); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_page.dart b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_page.dart new file mode 100644 index 00000000..3bd5c02f --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_page.dart @@ -0,0 +1,74 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'wallet_receive_amout/wallet_receive_amount.dart'; +import 'wallet_receive_request/wallet_receive_request.dart'; +import 'wallet_receive_type/wallet_receive_type.dart'; + +class WalletReceivePage extends ConsumerStatefulWidget { + const WalletReceivePage({ + super.key, + }); + + @override + ConsumerState createState() => _WalletReceivePageState(); +} + +class _WalletReceivePageState extends ConsumerState + with TickerProviderStateMixin { + final PageController _horizontalPageController = PageController( + initialPage: 0, + keepPage: true, + ); + + bool horizontalScrollLock = false; + + _navigateToNextPage() { + if (_horizontalPageController.page != null) { + _horizontalPageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + } + + _navigateToPreviousPage() { + if (_horizontalPageController.page != null) { + _horizontalPageController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + } + + @override + Widget build(BuildContext context) { + return PageView( + controller: _horizontalPageController, + physics: horizontalScrollLock + ? const NeverScrollableScrollPhysics() + : const AlwaysScrollableScrollPhysics(), + children: [ + WalletReceiveType( + doneCallback: () { + _navigateToNextPage(); + }, + ), + WalletReceiveAmount( + title: "Receive", + backCallback: () { + _navigateToPreviousPage(); + }, + doneCallback: () { + _navigateToNextPage(); + }, + ), + WalletReceiveRequest( + backCallback: () { + _navigateToPreviousPage(); + }, + ), + ], + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_request/wallet_receive_request.dart b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_request/wallet_receive_request.dart new file mode 100644 index 00000000..69dcda9b --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_request/wallet_receive_request.dart @@ -0,0 +1,373 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:pretty_qr_code/pretty_qr_code.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../../../../config/palette.dart'; +import '../../../../../helpers/wallet_number_formatting.dart'; + +import '../../../../atoms/copy_to_clipboard.dart'; +import '../../../../atoms/long_button.dart'; +import '../wallet_receive_state_provider.dart'; + +class WalletReceiveRequest extends ConsumerWidget { + final Function backCallback; + const WalletReceiveRequest({ + super.key, + required this.backCallback, + }); + + showSnackBar(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: Theme.of(context).colorScheme.error, + content: Text( + message, + style: TextStyle(color: Colors.white), + ), + ), + ); + } + + _launchBolt11Url(String url) async { + final Uri uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.platformDefault); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(walletRecieverProvider); + final rcvNotifier = ref.read(walletRecieverProvider.notifier); + + if (state.request != null && state.method == RecieveMethods.bolt11) { + _launchBolt11Url(state.request!); + } + + return Scaffold( + backgroundColor: Colors.black, + body: Column( + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + /// header back, close + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon( + Icons.arrow_back, + color: Colors.white, + size: 24, + ), + onPressed: () => backCallback(), + ), + Text( + 'receive', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + IconButton( + icon: Icon( + Icons.close, + color: Paletter.lightGray, + size: 24, + ), + onPressed: () { + rcvNotifier.reset(); + + context.pop(); + }, + ), + ], + ), + SizedBox(height: 40), + + if (state.request != null && + state.method == RecieveMethods.bolt11) + Column( + children: [ + Stack( + children: [ + Container( + constraints: BoxConstraints( + maxHeight: 300, + ), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: PrettyQrView.data( + data: state.request!, + decoration: const PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + color: Colors.black, + ), + background: Colors.white, + ), + ), + ), + if (state.isSuccess) + Container( + constraints: BoxConstraints( + maxHeight: 300, + maxWidth: 300, + ), + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + decoration: BoxDecoration( + color: + Colors.black.withValues(alpha: 0.7), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + PhosphorIcons.checkCircle(), + color: Colors.green, + size: 64, + ), + SizedBox(height: 8), + Text( + 'Request has been payed', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ], + ), + ), + ), + ), + ], + ), + SizedBox(height: 16), + Container( + constraints: BoxConstraints( + maxWidth: 300, + ), + child: CopyClipboardButton( + value: state.request!, + copyText: "Copy invoice", + backgroundColor: Paletter.extraDarkGray, + ), + ), + ], + ), + ], + ), + ), + ), + ), + + /// main + Expanded( + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + if (state.requestErr != null) + Text( + state.requestErr!, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 14, + ), + ), + SizedBox(height: 16), + _buildDetailRow( + label: "Amount", + value: state.amount != null + ? WalletNumberFormatting.formatAmount( + amount: state.amount!, unit: state.unit!) + : "Not set", + ), + _buildDetailRow( + label: "Unit", + value: state.unit ?? "Not set", + ), + _buildDetailRow( + label: "Memo", + value: state.memo ?? "", + ), + Spacer(), + ], + ), + ), + ), + ], + ), + bottomNavigationBar: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: longButton( + name: "close", + onPressed: () => {rcvNotifier.reset(), context.pop()}, + inverted: true), + ), + ), + ); + } + + Widget _buildDetailRow({ + required String label, + required String value, + }) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: TextStyle(color: Colors.grey, fontSize: 12)), + Text(value, style: TextStyle(color: Colors.white, fontSize: 16)), + ], + ), + ], + ), + ); + } +} + +class ContactReciever extends StatelessWidget { + const ContactReciever({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + backgroundColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), + child: + Text('NA', style: TextStyle(color: Colors.white, fontSize: 12)), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('receiver', + style: TextStyle(color: Colors.grey, fontSize: 12)), + Text('NOT IMPLEMENTED', + style: TextStyle(color: Colors.white, fontSize: 16)), + Text('send to pubkey not implemented', + style: TextStyle(color: Colors.grey, fontSize: 14)), + ], + ), + ), + ], + ), + ); + } +} + +class TokenReciever extends StatelessWidget { + const TokenReciever({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + backgroundColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), + child: + Text('TK', style: TextStyle(color: Colors.white, fontSize: 12)), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('receiver', + style: TextStyle(color: Paletter.gray, fontSize: 12)), + Text('Token', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold)), + Text('send as a token or qr code', + style: TextStyle(color: Paletter.lightGray, fontSize: 14)), + ], + ), + ), + ], + ), + ); + } +} + +class WalletReciever extends StatelessWidget { + const WalletReciever({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + backgroundColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), + child: + Text('NA', style: TextStyle(color: Colors.white, fontSize: 12)), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('receiver', + style: TextStyle(color: Colors.grey, fontSize: 12)), + Text('NOT IMPLEMENTED', + style: TextStyle(color: Colors.white, fontSize: 16)), + Text('send to wallet not implemented', + style: TextStyle(color: Colors.grey, fontSize: 14)), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_state_provider.dart b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_state_provider.dart new file mode 100644 index 00000000..151df57a --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_state_provider.dart @@ -0,0 +1,189 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:ndk/ndk.dart'; +import 'package:bolt11_decoder/bolt11_decoder.dart'; + +import '../../../providers/ndk_provider.dart'; + +enum RecieveMethods { + ecashToken('ecashToken'), + bolt11('bolt11'), + wallet('wallet'); + + final String value; + + const RecieveMethods(this.value); + + factory RecieveMethods.fromValue(String value) { + return RecieveMethods.values.firstWhere( + (kind) => kind.value == value, + orElse: () => throw ArgumentError('Invalid event kind value: $value'), + ); + } + + @override + String toString() => value; +} + +class WalletPayRecieverState { + final String? recieveToWalletId; + final int? amount; + final String? unit; + final String? memo; + final RecieveMethods? method; + + /// the mint request (in case of bolt11 the invoice) + final String? request; + + final Bolt11PaymentRequest? decodedBolt11Request; + + final String? requestErr; + final bool isPending; + final bool isSuccess; + + WalletPayRecieverState({ + required this.recieveToWalletId, + this.amount, + this.unit, + this.memo, + this.method, + this.request, + this.requestErr, + this.isPending = false, + this.isSuccess = false, + this.decodedBolt11Request, + }); + + WalletPayRecieverState copyWith({ + String? recieveToWalletId, + int? amount, + String? unit, + String? memo, + RecieveMethods? method, + String? request, + Bolt11PaymentRequest? decodedBolt11Request, + String? requestErr, + bool? isPending, + bool? isSuccess, + }) { + return WalletPayRecieverState( + recieveToWalletId: recieveToWalletId ?? this.recieveToWalletId, + amount: amount ?? this.amount, + unit: unit ?? this.unit, + memo: memo ?? this.memo, + method: method ?? this.method, + request: request ?? this.request, + decodedBolt11Request: decodedBolt11Request ?? this.decodedBolt11Request, + requestErr: requestErr ?? this.requestErr, + isPending: isPending ?? this.isPending, + isSuccess: isSuccess ?? this.isSuccess, + ); + } +} + +class WalletPayToNotifier extends StateNotifier { + final Ndk _ndk; + + WalletPayToNotifier({ + String? initialWalletId, + required Ndk ndk, + }) : _ndk = ndk, + super( + WalletPayRecieverState( + recieveToWalletId: initialWalletId, + ), + ); + + void updateRecieveToWalletId(String? walletId) { + state = state.copyWith( + recieveToWalletId: walletId, + unit: state.unit == null ? "sat" : null, + ); + } + + void updateMethod(RecieveMethods? method) { + state = state.copyWith(method: method); + } + + void updateAmount(int? amount) { + state = state.copyWith(amount: amount); + } + + void updateUnit(String? unit) { + state = state.copyWith(unit: unit); + } + + void updateMemo(String? memo) { + state = state.copyWith(memo: memo); + } + + void reset() { + state = WalletPayRecieverState( + recieveToWalletId: null, + amount: null, + unit: null, + memo: null, + method: null, + ); + } + + Future mintEcashToken() async { + if (state.recieveToWalletId == null || + state.amount == null || + state.unit == null || + state.method != RecieveMethods.bolt11) { + throw ArgumentError('Invalid state for minting ecash token'); + } + + try { + final initTransaction = await _ndk.cashu.initiateFund( + mintUrl: state.recieveToWalletId!, + amount: state.amount!, + unit: state.unit!, + method: state.method!.toString(), + memo: state.memo, + ); + + state = state.copyWith( + request: initTransaction.qoute!.request, + decodedBolt11Request: + Bolt11PaymentRequest(initTransaction.qoute!.request), + isPending: true, + ); + + final resultStream = + _ndk.cashu.retrieveFunds(draftTransaction: initTransaction); + + await for (final result in resultStream) { + if (result.state == ndk_entities.WalletTransactionState.completed) { + state = state.copyWith( + isPending: false, + isSuccess: true, + ); + } else if (result.state == ndk_entities.WalletTransactionState.failed) { + state = state.copyWith( + requestErr: result.completionMsg, + isPending: false, + isSuccess: false, + ); + } + } + } catch (e) { + state = state.copyWith( + requestErr: e.toString(), + isPending: false, + isSuccess: false, + ); + return; + } + } +} + +final walletRecieverProvider = + StateNotifierProvider((ref) { + final ndk = ref.watch(ndkProvider); + + return WalletPayToNotifier( + ndk: ndk, + ); +}); diff --git a/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_type/wallet_receive_type.dart b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_type/wallet_receive_type.dart new file mode 100644 index 00000000..f229af03 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_receive/wallet_receive_type/wallet_receive_type.dart @@ -0,0 +1,245 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../../../config/palette.dart'; + +import '../../../../../helpers/wallet_number_formatting.dart'; +import '../../../../atoms/long_button.dart'; + +import '../../wallet_providers/wallet_combined_state_provider.dart'; +import '../rcv_completers/wallet_rcv_ecash_completer_state_provider.dart'; +import '../wallet_receive_state_provider.dart'; + +class WalletReceiveType extends ConsumerWidget { + final Function doneCallback; + + const WalletReceiveType({ + super.key, + required this.doneCallback, + }); + + _onPasteToken(BuildContext context, WidgetRef ref) async { + final userClipboard = await _handleReadClipboard(); + if (userClipboard == null || userClipboard.isEmpty) { + _showError(context, 'Clipboard is empty or invalid'); + return; + } + + if (!userClipboard.startsWith("cashuB")) { + _showError(context, 'Invalid ecash token format'); + return; + } + + final ecashCompleter = + ref.read(walletReceiveEcashCompleterProvider.notifier); + + ecashCompleter.receiveEcash(tokenString: userClipboard); + if (!context.mounted) return; + context.pushReplacement('/wallet/receive/ecash'); + } + + Future _handleReadClipboard() async { + try { + ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); + return data?.text; + } catch (e) { + return null; + } + } + + _showError(BuildContext context, String message) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message, style: TextStyle(color: Colors.white)), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final combinedWallets = ref.watch(walletCombinedProvider); + final state = ref.watch(walletRecieverProvider); + final stateNotifier = ref.watch(walletRecieverProvider.notifier); + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + title: const Text('Receive'), + leading: Container(), + leadingWidth: 0, + actions: [ + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + stateNotifier.reset(); + Navigator.pop(context); + }, + ), + ], + ), + body: SafeArea( + child: Column( + children: [ + // scrollable results + Expanded( + child: ListView( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), + children: [ + _Island( + title: "Lightning", + child: longButton( + name: "create invoice", + onPressed: () { + stateNotifier.updateMethod(RecieveMethods.bolt11); + doneCallback(); + }, + ), + ), + _Island( + title: "Ecash", + child: longButton( + name: "paste token", + onPressed: () => _onPasteToken(context, ref), + )), + _Island( + title: 'All wallets', + child: _WalletsList( + wallets: combinedWallets.wallets + .where( + (w) => w.id != state.recieveToWalletId, + ) + .toList(), + balances: combinedWallets.balances, + selectedWalletId: state.recieveToWalletId, + onTap: (wallet) { + stateNotifier.updateRecieveToWalletId(wallet.id); + stateNotifier.updateMethod(RecieveMethods.wallet); + doneCallback(); + }, + ), + ), + ], + ), + ), + ], + ), + ), + + /// next button + bottomNavigationBar: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), + child: SizedBox( + width: double.infinity, + height: 40, + child: longButton( + name: "next", + onPressed: () { + if (state.memo == null) { + stateNotifier.updateMethod(RecieveMethods.bolt11); + } + + doneCallback(); + }, + inverted: true, + ), + ), + ), + ), + ); + } +} + +class _Island extends StatelessWidget { + final String title; + final Widget child; + const _Island({required this.title, required this.child}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + child, + ], + ), + ), + ); + } +} + +class _WalletsList extends StatelessWidget { + final List wallets; + final List balances; + final String? selectedWalletId; + final void Function(ndk_entities.Wallet) onTap; + + const _WalletsList({ + required this.wallets, + required this.balances, + required this.selectedWalletId, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + if (wallets.isEmpty) { + return const Padding( + padding: EdgeInsets.all(12.0), + child: Text('No wallets found'), + ); + } + return ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: wallets.length, + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (context, i) { + final w = wallets[i]; + final wBallances = balances.where((b) => b.walletId == w.id); + final selected = w.id == selectedWalletId; + + /// dont show selected wallet + if (selected) { + return Container(); + } + + return ListTile( + title: Text(w.name), + subtitle: Text(w.type.toString()), + + /// show all balances for the wallet + trailing: Column(children: [ + for (final b in wBallances) + Text( + "${WalletNumberFormatting.formatAmount(amount: b.amount, unit: b.unit)} ${b.unit}", + style: TextStyle( + color: Colors.white, + ), + ), + ]), + + onTap: () => onTap(w), + ); + }, + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_detail_page.dart b/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_detail_page.dart new file mode 100644 index 00000000..d6e9bfa2 --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_detail_page.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../../config/palette.dart'; + +class WalletTransactionDetailPage extends ConsumerWidget { + final ndk_entities.WalletTransaction transaction; + + const WalletTransactionDetailPage({ + super.key, + required this.transaction, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + title: Text('Transaction Detail'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Transaction ID: ${transaction.id}', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + 'Wallet ID: ${transaction.walletId}', + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 8), + Text( + 'Change Amount: ${transaction.changeAmount} ${transaction.unit}', + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 8), + Text( + 'Wallet Type: ${transaction.walletType}', + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 8), + Text( + 'State: ${transaction.state}', + style: TextStyle(fontSize: 16), + ), + if (transaction.completionMsg != null) ...[ + SizedBox(height: 8), + Text( + 'Completion Message: ${transaction.completionMsg}', + style: TextStyle(fontSize: 16), + ), + ], + if (transaction.transactionDate != null) ...[ + SizedBox(height: 8), + Text( + 'Transaction Date: ${transaction.transactionDate}', + style: TextStyle(fontSize: 16), + ), + ], + if (transaction.initiatedDate != null) ...[ + SizedBox(height: 8), + Text( + 'Initiated Date: ${transaction.initiatedDate}', + style: TextStyle(fontSize: 16), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_list_page.dart b/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_list_page.dart new file mode 100644 index 00000000..4d5017be --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_list_page.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +import 'package:ndk/entities.dart' as ndk_entities; + +import '../../../../config/palette.dart'; +import '../../../atoms/wallet/wallet_transaction_card.dart'; +import '../wallet_navigation.dart'; +import 'wallet_transaction_list_state_provider.dart'; + +class WalletTransactionListPage extends ConsumerWidget { + const WalletTransactionListPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(walletTransactionListProvider); + + final grouped = _groupByDay(state.transactions); + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: AppBar( + elevation: 0, + backgroundColor: Theme.of(context).colorScheme.surface, + foregroundColor: Colors.white, + title: const Text('Transactions'), + leading: IconButton( + icon: Icon(PhosphorIcons.caretLeft(), size: 24), + onPressed: () { + ref.read(walletNavigationProvider.notifier).changeMainPage(0); + }, + )), + body: state.transactions.isEmpty + ? const _EmptyTransactions() + : ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), + itemCount: grouped.length, + itemBuilder: (context, index) { + final section = grouped[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _DateHeader(label: section.label), + const SizedBox(height: 8), + ...section.items.map( + (tx) => WalletTransactionCard( + tx: tx, + showDate: false, + ), + ), + const SizedBox(height: 16), + ], + ); + }, + ), + ); + } +} + +DateTime _fromUnixSeconds(int seconds) => + DateTime.fromMillisecondsSinceEpoch(seconds * 1000, isUtc: true).toLocal(); + +String _formatDayLabel(DateTime date) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final thatDay = DateTime(date.year, date.month, date.day); + + if (thatDay == today) return 'Today'; + if (thatDay == today.subtract(const Duration(days: 1))) return 'Yesterday'; + + return DateFormat('d.M.yyyy').format(date); +} + +class _DaySection { + final String label; + final List items; + _DaySection({required this.label, required this.items}); +} + +List<_DaySection> _groupByDay( + List transactions, +) { + final Map> buckets = {}; + final Map labelToDate = {}; + + for (final tx in transactions) { + final dt = _fromUnixSeconds(_bestDate(tx) ?? 0); + // grouping key + final key = DateFormat('yyyy-MM-dd').format(dt); + buckets.putIfAbsent(key, () => []).add(tx); + labelToDate[key] = DateTime(dt.year, dt.month, dt.day); + } + + final keys = buckets.keys.toList() + ..sort((a, b) => labelToDate[b]!.compareTo(labelToDate[a]!)); + + return keys + .map((k) => _DaySection( + label: _formatDayLabel(labelToDate[k]!), + items: buckets[k]!, + )) + .toList(); +} + +int? _bestDate(ndk_entities.WalletTransaction tx) => + tx.transactionDate ?? tx.initiatedDate; + +class _DateHeader extends StatelessWidget { + final String label; + const _DateHeader({required this.label}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Divider(color: Paletter.darkGray.withValues(alpha: 0.4))), + Container( + margin: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Paletter.extraDarkGray, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Paletter.darkGray.withValues(alpha: 0.4)), + ), + child: Text( + label, + style: TextStyle( + color: Colors.white, + fontSize: 12.5, + fontWeight: FontWeight.w600, + letterSpacing: 0.2, + ), + ), + ), + Expanded( + child: Divider(color: Paletter.darkGray.withValues(alpha: 0.4))), + ], + ); + } +} + +class _EmptyTransactions extends StatelessWidget { + const _EmptyTransactions(); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 48), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.receipt_long_outlined, size: 48, color: Paletter.gray), + const SizedBox(height: 12), + Text( + 'No transactions available', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 6), + Text( + 'Your recent activity will show up here.', + textAlign: TextAlign.center, + style: TextStyle( + color: Paletter.lightGray, + fontSize: 13.5, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_list_state_provider.dart b/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_list_state_provider.dart new file mode 100644 index 00000000..5b5de36d --- /dev/null +++ b/lib/presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_list_state_provider.dart @@ -0,0 +1,82 @@ +import 'package:ndk/entities.dart' as ndk_entities; +import 'package:ndk/ndk.dart'; +import 'package:riverpod/riverpod.dart'; + +import '../../../providers/ndk_provider.dart'; +import '../wallet_providers/wallet_combined_state_provider.dart'; + +const limit = 20; + +class WalletTransactionListState { + final List transactions; + final List pendingTransactions; + final int offset; + + WalletTransactionListState({ + required this.transactions, + required this.pendingTransactions, + this.offset = 0, + }); + + WalletTransactionListState copyWith({ + List? transactions, + List? pendingTransactions, + int? offset, + }) { + return WalletTransactionListState( + transactions: transactions ?? this.transactions, + pendingTransactions: pendingTransactions ?? this.pendingTransactions, + offset: offset ?? this.offset, + ); + } +} + +class WalletTransactionListNotifier + extends StateNotifier { + final Ndk _ndk; + final Ref ref; + WalletTransactionListNotifier({required Ndk ndk, required this.ref}) + : _ndk = ndk, + super( + WalletTransactionListState( + transactions: [], + pendingTransactions: [], + ), + ) { + ref.listen(walletCombinedProvider, (previous, next) { + if (next.pendingTransactions != previous?.pendingTransactions || + next.recentTransactions != previous?.recentTransactions) { + state = state.copyWith( + pendingTransactions: next.pendingTransactions, + ); + } + }, fireImmediately: true); + _loadMore(); + } + + _loadMore() async { + final transactions = await _ndk.wallets.combinedTransactions( + limit: limit, + offset: state.offset, + ); + state = state.copyWith( + transactions: [...state.transactions, ...transactions], + offset: state.offset + transactions.length, + ); + } + + void reset() { + state = WalletTransactionListState( + transactions: [], + pendingTransactions: [], + ); + } +} + +final walletTransactionListProvider = StateNotifierProvider< + WalletTransactionListNotifier, WalletTransactionListState>( + (ref) { + final ndk = ref.watch(ndkProvider); + return WalletTransactionListNotifier(ndk: ndk, ref: ref); + }, +); diff --git a/lib/routes.dart b/lib/routes.dart index d41efbb3..c0a3b2c4 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,8 +1,17 @@ import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; +import 'package:ndk/entities.dart' as ndk_entities; import 'domain_layer/entities/starter_pack_identifier.dart'; import 'lifecycle/app_init_shell.dart'; +import 'presentation_layer/routes/wallet/add_mint/add_mint_page.dart'; +import 'presentation_layer/routes/wallet/mint_info/mint_info_page.dart'; +import 'presentation_layer/routes/wallet/wallet_navigation.dart'; +import 'presentation_layer/routes/wallet/wallet_pay/wallet_pay_done/wallet_pay_done.dart'; +import 'presentation_layer/routes/wallet/wallet_pay/wallet_pay_page.dart'; +import 'presentation_layer/routes/wallet/wallet_receive/wallet_receive_page.dart'; +import 'presentation_layer/routes/wallet/wallet_receive/rcv_completers/wallet_rcv_ecash_completer_page.dart'; +import 'presentation_layer/routes/wallet/wallet_transaction/wallet_transaction_detail_page.dart'; import 'presentation_layer/components/app_bottom_navigation_bar/app_bottom_navigation_bar.dart'; import 'presentation_layer/components/drawer/nostr_side_menu.dart'; import 'presentation_layer/components/drawer/nostr_side_menu_post_button.dart'; @@ -178,6 +187,52 @@ final routes = [ starterPackIdentifier: state.extra as StarterPackIdentifier, ), ), + // Wallet routes + GoRoute( + path: '/wallet/dashboard', + builder: (context, state) => WalletNavigation( + title: "a", + ), + ), + GoRoute( + path: '/wallet/receive', + builder: (context, state) => WalletReceivePage(), + ), + GoRoute( + path: '/wallet/receive/ecash', + builder: (context, state) => WalletReceiveEcashCompleterPage(), + ), + GoRoute( + path: '/wallet/mints', + builder: (context, state) => WalletNavigation( + title: "a", + ), + ), + GoRoute( + path: '/wallet/pay', + builder: (context, state) => WalletPayPage(), + ), + GoRoute( + path: '/wallet/add_mint', + builder: (context, state) => const AddMintPage(), + ), + GoRoute( + path: '/wallet/mint_details', + builder: (context, state) => MintInfoPage( + mintUrl: state.extra as String?, + ), + ), + GoRoute( + path: '/wallet/transactions/detail', + builder: (context, state) => WalletTransactionDetailPage( + transaction: state.extra as ndk_entities.WalletTransaction, + ), + ), + + GoRoute( + path: '/wallet/pay/done', + builder: (context, state) => WalletPayDone(), + ), ], ), // Routes outside the shell (no persistent layout) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3b97ffd3..96fb7612 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,6 +13,7 @@ import firebase_core import firebase_messaging import flutter_local_notifications import flutter_secure_storage_macos +import mobile_scanner import objectbox_flutter_libs import package_info_plus import path_provider_foundation @@ -34,6 +35,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 886e1b7b..9ee6cf36 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -84,10 +84,10 @@ packages: dependency: transitive description: name: archive - sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.0.7" args: dependency: transitive description: @@ -96,6 +96,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" + ascii_qr: + dependency: transitive + description: + name: ascii_qr + sha256: "2046e400a0fa4ea0de5df44c87b992cdd1f76403bb15e64513b89263598750ae" + url: "https://pub.dev" + source: hosted + version: "1.0.1" async: dependency: transitive description: @@ -112,6 +120,13 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + bc_ur_dart: + dependency: "direct main" + description: + path: "../fx-wallet-packages/packages/bc_ur_dart" + relative: true + source: path + version: "0.1.19" bech32: dependency: "direct main" description: @@ -129,6 +144,15 @@ packages: url: "https://github.com/5eeman/bip32-dart.git" source: git version: "2.0.1" + bip32_keys: + dependency: transitive + description: + path: "." + ref: HEAD + resolved-ref: b5a0342220e7ee5aaf64d489a589bdee6ef8de22 + url: "https://github.com/1-leo/dart-bip32-keys" + source: git + version: "3.1.2" bip340: dependency: "direct main" description: @@ -141,10 +165,18 @@ packages: dependency: "direct main" description: name: bip39_mnemonic - sha256: e36c1f1fecd938d433b8f7a1e74d5bc721e16816b59dbac5b31307f079d3b3bc + sha256: dd6bdfc2547d986b2c00f99bba209c69c0b6fa5c1a185e1f728998282f1249d5 + url: "https://pub.dev" + source: hosted + version: "4.0.1" + bolt11_decoder: + dependency: "direct main" + description: + name: bolt11_decoder + sha256: ce3c4e4311e84c7b6ceb06ffbf84329bf6bdac1244ecfa1fc157c46038021bbc url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "1.0.2" boolean_selector: dependency: transitive description: @@ -165,10 +197,10 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "3.1.0" build_cli_annotations: dependency: transitive description: @@ -181,10 +213,10 @@ packages: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" build_daemon: dependency: transitive description: @@ -197,26 +229,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.7.1" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "9.3.1" built_collection: dependency: transitive description: @@ -229,10 +261,10 @@ packages: dependency: transitive description: name: built_value - sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb url: "https://pub.dev" source: hosted - version: "8.9.5" + version: "8.11.1" cached_network_image: dependency: "direct main" description: @@ -257,6 +289,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + cbor: + dependency: transitive + description: + name: cbor + sha256: f5239dd6b6ad24df67d1449e87d7180727d6f43b87b3c9402e6398c7a2d9609b + url: "https://pub.dev" + source: hosted + version: "6.3.7" characters: dependency: transitive description: @@ -269,10 +309,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" ci: dependency: transitive description: @@ -281,6 +321,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -317,10 +365,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99" + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.1.5" connectivity_plus_platform_interface: dependency: transitive description: @@ -341,10 +389,10 @@ packages: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.15.0" crop_your_image: dependency: "direct main" description: @@ -449,6 +497,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + decimal: + dependency: transitive + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" elliptic: dependency: transitive description: @@ -493,10 +549,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "8d938fd5c11dc81bf1acd4f7f0486c683fe9e79a0b13419e27730f9ce4d8a25b" + sha256: "09b474c0c8117484b80cbebc043801ff91e05cfbd2874d512825c899e1754694" url: "https://pub.dev" source: hosted - version: "9.2.1" + version: "9.2.3" file_selector_linux: dependency: transitive description: @@ -509,10 +565,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" url: "https://pub.dev" source: hosted - version: "0.9.4+2" + version: "0.9.4+3" file_selector_platform_interface: dependency: transitive description: @@ -639,10 +695,10 @@ packages: dependency: "direct main" description: name: flutter_launcher_icons - sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.14.3" + version: "0.14.4" flutter_link_previewer: dependency: "direct main" description: @@ -724,10 +780,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" + sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.0.29" flutter_portal: dependency: transitive description: @@ -764,10 +820,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_linux - sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" flutter_secure_storage_macos: dependency: transitive description: @@ -804,10 +860,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -919,14 +975,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + ieee754: + dependency: transitive + description: + name: ieee754 + sha256: "7d87451c164a56c156180d34a4e93779372edd191d2c219206100b976203128c" + url: "https://pub.dev" + source: hosted + version: "1.0.3" image: dependency: "direct main" description: name: image - sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.5.4" image_picker: dependency: "direct main" description: @@ -939,10 +1003,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9" + sha256: b08e9a04d0f8d91f4a6e767a745b9871bfbc585410205c311d0492de20a7ccd6 url: "https://pub.dev" source: hosted - version: "0.8.12+22" + version: "0.8.12+25" image_picker_for_web: dependency: transitive description: @@ -1068,14 +1132,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" - logger: - dependency: transitive - description: - name: logger - sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 - url: "https://pub.dev" - source: hosted - version: "2.6.2" logging: dependency: transitive description: @@ -1124,23 +1180,29 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95" + url: "https://pub.dev" + source: hosted + version: "7.0.1" mockito: dependency: "direct dev" description: name: mockito - sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 + sha256: "2314cbe9165bcd16106513df9cf3c3224713087f09723b128928dc11a4379f99" url: "https://pub.dev" source: hosted - version: "5.4.5" + version: "5.5.0" ndk: dependency: "direct main" description: - path: "packages/ndk" - ref: tmp-camelus-1-7-4 - resolved-ref: "1264d9bd65f4cc998ce776951217adb3a7118a9f" - url: "https://github.com/relaystr/ndk.git" - source: git - version: "0.6.0-dev.0" + path: "../ndk/packages/ndk" + relative: true + source: path + version: "0.6.0-dev.4" ndk_amber: dependency: "direct main" description: @@ -1152,11 +1214,10 @@ packages: ndk_objectbox: dependency: "direct main" description: - name: ndk_objectbox - sha256: "4651b2b4575b5bed1d619eed225234f3a68b72049bac1df426d9062625cf8e8a" - url: "https://pub.dev" - source: hosted - version: "0.2.6" + path: "../ndk/packages/objectbox" + relative: true + source: path + version: "0.2.7-dev.9" ndk_rust_verifier: dependency: "direct main" description: @@ -1185,26 +1246,26 @@ packages: dependency: "direct main" description: name: objectbox - sha256: "25c2e24b417d938decb5598682dc831bc6a21856eaae65affbc57cfad326808d" + sha256: "825b3106bbd8b15b2e7a08d75345a218db020fd87bd5dc246acae65ec08ebc06" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "5.0.0" objectbox_flutter_libs: dependency: "direct main" description: name: objectbox_flutter_libs - sha256: "574b0233ba79a7159fca9049c67974f790a2180b6141d4951112b20bd146016a" + sha256: f5fd388ffebda310a1acda5c0076f854d7c0cb49634710eb88937ea4ae6c8e10 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "5.0.0" objectbox_generator: dependency: "direct dev" description: name: objectbox_generator - sha256: "1b17e9168d03706b5bb895b5f36f4301aa7c973ac30ff761b205b1ca3e2e3865" + sha256: "62c1015e733267eef73b3f70f1b6ab3b483008714d8ad43b4c79af3a4d459474" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "5.0.0" octo_image: dependency: transitive description: @@ -1225,18 +1286,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "8.3.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" path: dependency: "direct main" description: @@ -1265,10 +1326,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -1361,10 +1422,18 @@ packages: dependency: transitive description: name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.3" + pretty_qr_code: + dependency: "direct main" + description: + name: pretty_qr_code + sha256: "2291db3f68d70a3dcd46c6bd599f30991ae4c02f27f36215fbb3f4865a609259" + url: "https://pub.dev" + source: hosted + version: "3.5.0" process: dependency: transitive description: @@ -1405,6 +1474,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" riverpod: dependency: "direct main" description: @@ -1521,34 +1598,34 @@ packages: dependency: "direct main" description: name: share_plus - sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0 + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef" + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.11" shared_preferences_foundation: dependency: transitive description: @@ -1638,10 +1715,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.1.0" source_map_stack_trace: dependency: transitive description: @@ -1694,10 +1771,10 @@ packages: dependency: transitive description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.6" sqflite_darwin: dependency: transitive description: @@ -1838,10 +1915,10 @@ packages: dependency: transitive description: name: timezone - sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.10.1" timing: dependency: transitive description: @@ -1862,34 +1939,34 @@ packages: dependency: transitive description: name: unorm_dart - sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b" + sha256: "8e3870a1caa60bde8352f9597dd3535d8068613269444f8e35ea8925ec84c1f5" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.1+1" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" + sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656" url: "https://pub.dev" source: hosted - version: "6.3.15" + version: "6.3.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" url_launcher_linux: dependency: transitive description: @@ -1918,10 +1995,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -1942,10 +2019,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 url: "https://pub.dev" source: hosted - version: "1.1.18" + version: "1.1.19" vector_graphics_codec: dependency: transitive description: @@ -1958,10 +2035,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331" url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.17" vector_math: dependency: transitive description: @@ -1982,34 +2059,34 @@ packages: dependency: transitive description: name: video_player_android - sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73" + sha256: d26f8791c8f670825cc227e2cad4319d2ac02b71b2ad5c2b67786bb873ac43b1 url: "https://pub.dev" source: hosted - version: "2.8.7" + version: "2.8.11" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "9ee764e5cd2fc1e10911ae8ad588e1a19db3b6aa9a6eb53c127c42d3a3c3f22f" + sha256: f52261d11f97bf14c43e8ed5714f71d8ce4538552b8cc87f45e5d87d3c205e41 url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.8.3" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 + sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.4.0" video_player_web: dependency: transitive description: name: video_player_web - sha256: e8bba2e5d1e159d5048c9a491bb2a7b29c535c612bb7d10c1e21107f5bd365ba + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" visibility_detector: dependency: "direct main" description: @@ -2030,10 +2107,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" web: dependency: transitive description: @@ -2046,10 +2123,10 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: "direct main" description: @@ -2114,6 +2191,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + xrandom: + dependency: transitive + description: + name: xrandom + sha256: d843a7f4004d91b5a0341a28a781fddaf92a5b377f776a06a08e7a9320e95ad3 + url: "https://pub.dev" + source: hosted + version: "0.7.2" xxh3: dependency: "direct main" description: @@ -2131,5 +2216,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.8.0 <4.0.0" flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index 97eb0e92..333bba0b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,14 +63,14 @@ dependencies: path_provider: ^2.1.4 pointycastle: ^4.0.0 #device_preview: ^1.1.0 - bip39_mnemonic: ^3.0.6 + bip39_mnemonic: ^4.0.1 bip32: ^2.0.0 crop_your_image: ^2.0.0 package_info_plus: ^8.0.2 rxdart: ^0.28.0 shimmer: ^3.0.0 flutter_force_directed_graph: ^1.0.7 - objectbox: ^4.3.0 + objectbox: ^5.0.0 objectbox_flutter_libs: any amberflutter: ^0.0.9 intl: any @@ -97,6 +97,10 @@ dependencies: flutter_chat_types: ^3.6.2 visibility_detector: ^0.4.0+2 video_player: ^2.10.0 + pretty_qr_code: ^3.5.0 + bc_ur_dart: ^0.1.19 + mobile_scanner: ^7.0.1 + bolt11_decoder: ^1.0.2 window_manager: ^0.5.1 go_router: ^16.2.4 timeago: ^3.7.1 @@ -109,18 +113,18 @@ dependencies: dependency_overrides: ndk: - #path: ../ndk/packages/ndk - git: - url: https://github.com/relaystr/ndk.git - path: packages/ndk - ref: tmp-camelus-1-7-4 + path: ../ndk/packages/ndk + #git: + # url: https://github.com/relaystr/ndk.git + # path: packages/ndk + # ref: tmp-camelus-1-7-2 #ndk_rust_verifier: # path: ../ndk/packages/rust_verifier #git: # url: https://github.com/relaystr/ndk.git # path: packages/rust_verifier - #ndk_objectbox: - #path: ../ndk/packages/objectbox + ndk_objectbox: + path: ../ndk/packages/objectbox #git: # url: https://github.com/relaystr/ndk.git # path: packages/objectbox @@ -132,6 +136,8 @@ dependency_overrides: # url: https://github.com/relaystr/ndk.git #path: packages/amber # ref: nip-59-gift-wrap + bc_ur_dart: + path: ../fx-wallet-packages/packages/bc_ur_dart bip32: git: url: https://github.com/5eeman/bip32-dart.git @@ -140,7 +146,7 @@ dependency_overrides: dev_dependencies: - build_runner: ^2.4.13 + build_runner: ^2.5.4 mockito: ^5.4.0 flutter_test: sdk: flutter @@ -176,6 +182,7 @@ flutter: - assets/images/ - assets/lottie/ - assets/app_icons/ + - assets/animations/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test/unit_test/helpers/helpers_test.dart b/test/unit_test/helpers/helpers_test.dart index d4fc7378..d142b23c 100644 --- a/test/unit_test/helpers/helpers_test.dart +++ b/test/unit_test/helpers/helpers_test.dart @@ -35,7 +35,7 @@ void main() { const hex = '1e1a76e8dc8f8c82eacb5ea02a1e87a2f172dd'; const hrp = 'test'; - final bech32 = helpers.encodeBech32(hex, hrp); + final bech32 = Helpers.encodeBech32(hex, hrp); final decoded = helpers.decodeBech32(bech32); expect(decoded[0], hex);